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