【python】多线程小结

    技术2023-08-14  107

    every blog every motto: Light tomorrow with today.

    0. 前言

    前不久有一个任务用到了多线程,也简单的学习了下,但是没有做相关记录,今天有空再复习一遍,并简单记录下来。 说明: 后续可能会增补

    1. 正文

    1.1 基本概念

    1>. 单线程&多线程

    现在我们有两个任务:烧水和拖地。 单线程: 先烧水,等水烧开,再进行拖地。等待烧水的过程的时间(资源)就白白浪费了。 多线程: 先烧水,烧水的过程(相当于IO操作)把时间(资源)让出来给拖地,地拖完了,水也烧开了。 以上就多线程的优势的直观理解

    2>. 线程和进程

    一个任务就是一个进程,比如打开浏览器、启动微信等有些进程同时能做多件事,比如,word可以同时进行打字、拼写检查、打印等,这里多件事,我们称为多个“子任务”,我们把这些“子任务“称为线程一个程序至少一个进程,一个进程至少一个线程。多线程可以共享全局变量

    3>. 并行&并发

    并行(parallel): 是计算机系统中能够同时执行两个或更多个处理的一种计算方法,可以同时工作于同一程序的不同方面,并行处理的主要目的是节省大型和复杂问题的解决时间。 并发(concurrent): 指同一时间段有几个程序都处于已启动运行和运行完毕之间。且这几个程序都是在同一个处理器(CPU)上运行,但任意时刻上只有一个程序在CPU上运行 并发是指一段时间内宏观上多个程序同时运行,并行是在某一时刻,真正有多个程序运行 小结:

    区别: 并发,多个事情,在同一时间段内同时发生。并行,多个事情,在同一时间点上同时发生。并发的多个任务之间时互相抢占资源的。并行的多个任务之间时不互相抢占资源的只有在多CPU或者一个CPU多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。

    4>. 同步&异步

    同步异步: 同步: 指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。 异步: 指进程不需要一直等待下去,而实继续执行下面的操作,不管其他进程的状态,当有消息返回时系统会通知进程进行处理,这样可以提高执行效率

    5>. GIL(全局解释锁,Global Interpreter Lock)

    GIL并不是python的特性,他是实现python解释器(C时引入的), 作用: 限制多线程同时执行,保证同一时刻只有一个线程执行 原因: 线程并非独立,在一个进程中多个线程共享变量,多个线程执行会导致数据被污染造成数据混乱,这个就是线程不安全性,为此引入互斥锁。 互斥锁: 即确保某段关键代码的数据只能有一个线程从头到尾完整运行,保证了这段代码数据的安全性,但这就导致了死锁。 死锁: 多个子线程在等待对方解除占用状态,但是都不先解锁,互相带灯,这就是死锁。

    GIL说白了就是为多线程,一个线程运行其他线程阻塞,使你的多线程代码不是同时执行,而实交替执行。 基于GIL的存在,在遇到大量IO操作(文件读写、网络等待)时,使用多线程效率更高。

    1.2 多线程部分

    用装饰器查看函数运行时间,有关装饰器介绍,点我

    import time import functools c = 0 # 全局变量 def count_time(func): """装饰器:计算程序运行时间""" @functools.wraps(func) def wrapper(*args, **kwargs): t1 = time.time() res = func(*args, **kwargs) t2 = time.time() # 计算时间 time_consumed = t2 - t1 print('{}函数一共花费了{}秒'.format(func.__name__, time_consumed)) return res return wrapper

    1.2.1 测试主体为两个函数

    两个被测试的函数:

    @count_time def mop_floor(arg): """拖地操作,""" global c print('开始拖地……') for i in range(3): time.sleep(1) c += 5 c -= 5 print('{} is running……c={} !!'.format(arg, c)) print('---------地拖完了!-----------') @count_time def heat_water(arg): """烧水操作""" global c print('我要烧水了……') for i in range(4): time.sleep(1) print('{} is running……c={} !!'.format(arg, c)) print('------------水烧开了!-------------')

    >1. 单线程

    单线程主程序:

    @count_time def single_thread(): """单线程程序""" mop_floor('1') heat_water('2') def main(): # 单线程 single_thread() # my_processing() if __name__ == '__main__': main()

    运行结果: 可以看到两个函数依次执行,程序总耗时 7秒

    >2. 多线程

    @count_time def my_thread(): # mop_floor('1') # heat_water('2') t1 = threading.Thread(target=mop_floor, args=('1',)) t2 = threading.Thread(target=heat_water, args=('2',)) # t1.setDaemon(True) # t2.setDaemon(True) t1.start() t2.start() t1.join() t2.join() print('-' * 100) def main(): # 单线程 # single_thread() # 多线程 my_thread() if __name__ == '__main__': main()

    运行结果: 程序总耗时4秒,可以发现,总的运行时间减少了 说明: join: join方法让主线程阻塞,等待其创建的线程执行完成

    3>. 尝试无join

    对多线程主体程序进行修改

    @count_time def my_thread(): # mop_floor('1') # heat_water('2') t1 = threading.Thread(target=mop_floor, args=('1',)) t2 = threading.Thread(target=heat_water, args=('2',)) t1.start() t2.start() print('-' * 100)

    结果: 不加join ,当主线程执行完毕之后,当前程序并不会结束,必须等到所有线程都结束之后才能结束当前进程。

    4>. 尝试守护线程(daemon)

    可以通过将创建的线程指定为守护线程(daemon) ,这样主线程执行完毕之后会立即结束当前为执行的线程,然后结束程序。 守护线程作用: 如果当前python线程是守护线程,那么意味着这个线程是 “不重要” 的,”不重要“意味着如果他的主线程结束了但该守护线程没有运行完,守护线程会被强制结束。

    @count_time def my_thread(): # mop_floor('1') # heat_water('2') t1 = threading.Thread(target=mop_floor, args=('1',)) t2 = threading.Thread(target=heat_water, args=('2',)) t1.setDaemon(True) t2.setDaemon(True) t1.start() t2.start() # t1.join() # t2.join() print('-' * 100)

    主线程执行完毕以后会立即结束未执行的线程。

    1.2.2 测试主体为一个计算型函数

    被测试函数为一个计算型函数

    @count_time def single_func_tested(arg): """对一个函数进行测试""" sum = 0 for i in range(10000000): sum += i print('{} over'.format(arg))

    >1. 单线程

    单线程

    @count_time def single_thread(): """单线程程序""" # 对一个函数进行测试 single_func_tested('1') print('-'*100)

    主函数

    def main(): # 单线程 single_thread() if __name__ == '__main__': main()

    结果

    >2. 多线程

    多线程程序

    @count_time def my_thread(): thread_array = {} n = 1 for tid in range(n): t = threading.Thread(target=single_func_tested, args=('1',)) t.start() thread_array[tid] = t for i in range(n): thread_array[i].join() print('-' * 100) def main(): # 单线程 # single_thread() # 多线程 my_thread() # my_processing() if __name__ == '__main__': main()

    测试与结果: 当n=1时: 当 n=2 时, 当n=3时, 对单个函数开多线程小结: 由以上可以发现,随着线程数的增加,总运行时间并没有减少反而增加了,同时,由打印结果可以发现,对单个函数开多线程仅是对单个函数重复执行几遍而已

    1.2.2 测试主体为一个等待型函数

    被测试函数为一个I/O型函数

    @count_time def mop_floor(arg): """拖地操作,""" global c print('开始拖地……') for i in range(3): time.sleep(1) c += 5 c -= 5 print('{} is running……c={} !!'.format(arg, c)) print('---------地拖完了!-----------')

    >1. 单线程

    单线程

    @count_time def single_thread(): """单线程程序""" mop_floor('1') print('-'*100)

    主函数

    def main(): # 单线程 single_thread() # 多线程 # my_thread() # my_processing() if __name__ == '__main__': main()

    >2. 多线程

    多线程函数

    @count_time def my_thread(): thread_array = {} n = 3 for tid in range(n): t = threading.Thread(target=mop_floor, args=('1',)) t.start() thread_array[tid] = t for i in range(n): thread_array[i].join() print('-' * 100)

    主函数:

    def main(): # 多线程 my_thread() if __name__ == '__main__': main()

    由以上可以发现,当n=3时,运行时间和单线相同(其中,n=1,2结果亦是相同,读者可自行i验证),和上面的单个计算型函数一样,对单个等待型函数也是将其重复执行罢了,但不同的时,单个等待型函数总运行时间和单线程运行时间相同,并未随n的增加而增加。 进一步总结:

    对单个函数开多线程无意义,仅将其重复多遍而已个人猜测: 单个计算型函数 对应 CPU密集型单个等待型函数 对应 I/O密集型 这篇文章对单个函数测试开多线程,并不能说明问题。

    1.2.2 测试主体为一个函数(改进版)

    版本一:

    @count_time def single_func_tested(arg): """对一个函数进行测试""" sum = 0 for i in range(100): # 100000 sum += i time.sleep(0.1) for i in range(100): sum += i print('{} over'.format(arg))

    版本二:

    @count_time def single_func_tested(arg): """对一个函数进行测试""" sum = 0 for i in range(100): # 100000 sum += i time.sleep(0.1) for i in range(100): sum += i print('{} over'.format(arg))

    注:

    以上版本,不贴具体结果,由兴趣的读者,可自行尝试。经实验后发现,使用多线程均不能缩短程序运行时间。得出对单个程序使用多线程无效(此观点可锤,欢迎)

    1.3 源码

    https://gist.github.com/onceone/f5489dcc1499c81ee0161f0ea3bb442b

    参考文献

    [1] https://blog.csdn.net/weixin_39190382/article/details/107107980 [2] http://www.uml.org.cn/python/201901221.asp [3] https://www.jianshu.com/p/644dbb6d4cc8 [4] https://blog.csdn.net/m0_37324740/article/details/85765167 [5] https://www.cnblogs.com/-qing-/p/11291581.html [6] https://blog.csdn.net/lzy98/article/details/88819425 [7] https://www.cnblogs.com/yssjun/p/11302500.html [8] https://www.jb51.net/article/167165.htm [9] https://blog.csdn.net/weixin_44850984/article/details/89165731 [10] https://www.cnblogs.com/justbreaking/p/7218909.html?utm_source=itdadao&utm_medium=referral

    Processed: 0.009, SQL: 9