GIL: cpython的一个全局解释器锁(解决并发数据安全共享问题),规定同一时间只允许一个线程进入CPU。所以python多线程是并发。
python.exe是一个进程,py是一个主线程,t1,t2是子线程
join阻塞函数
# -*- coding:utf-8 -*-
import threading
import time
def foo(something):
for i in range(5):
time.sleep(1)
print("正在做:%s" % something)
t1 = threading.Thread(target=foo, args=("数据逻辑1",))
t2 = threading.Thread(target=foo, args=("数据逻辑2",))
t1.start()
t2.start()
# 在子线程完成任务前,这个子线程的父线程一直被阻塞
t1.join()
t2.join()
# 如果没加join()函数,主线程,t1,t2这三个线程互不干扰,交替运行
print("主线程启动数据检查任务。。。")
结果:
正在做:数据逻辑2
正在做:数据逻辑1
正在做:数据逻辑1正在做:数据逻辑2
正在做:数据逻辑1正在做:数据逻辑2
正在做:数据逻辑2
正在做:数据逻辑1
正在做:数据逻辑1正在做:数据逻辑2
主线程任务。。。
守护线程
在一个含有线程的python程序中,当主线程的代码运行完之后,如果还有其他子线程还未执行完毕,那么主线程会等待子线程执行完毕之后,再结束;如果有一个线程必须设置为无限循环,那么该线程不结束,意味着整个python程序就不能结束,那为了能够让python程序正常退出,将这类无限循环的线程设置为 守护线程 ,当程序当中仅仅剩下守护线程时,python程序就能够正常退出,不必关心这类线程是否执行完毕,这就是守护线程的意义。
# -*- coding:utf-8 -*-
import threading
import time
def foo(something):
for i in range(5):
time.sleep(1)
print("正在做:%s" % something)
t1 = threading.Thread(target=foo, args=("数据逻辑1",))
t2 = threading.Thread(target=foo, args=("数据逻辑2",))
# 声明守护线程,必须在start方法调用前声明
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
for i in range(5):
time.sleep(1)
print("消费数据")
print("消费数据已经满足了")
结果:
正在做:数据逻辑2
正在做:数据逻辑1
消费数据
正在做:数据逻辑1正在做:数据逻辑2
消费数据
正在做:数据逻辑2正在做:数据逻辑1消费数据
正在做:数据逻辑2消费数据正在做:数据逻辑1
正在做:数据逻辑1
正在做:数据逻辑2
消费数据
消费数据已经满足了
死锁
# 两个线程分别占用一部分资源,并且同时等待对方的资源
# 指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象。
import threading, time
lockA = threading.Lock() # 解释动作锁
lockB = threading.Lock() # offer锁
# 面试官
def foo1():
lockA.acquire() # 上锁,面试官等待的资源,
print("请解释什么是死锁")
time.sleep(1)
lockB.acquire() # 上锁,面试官的资源
print("发offer")
time.sleep(1)
lockA.release()
lockB.release()
# 小明
def foo2():
lockB.acquire() # 上锁,小明等待的资源
print("请给我发offer")
time.sleep(1)
lockA.acquire() # 上锁,小明的资源
print("向面试官解释什么是死锁")
time.sleep(1)
lockA.release()
lockB.release()
t1 = threading.Thread(target=foo1)
t2 = threading.Thread(target=foo2)
t1.start()
t2.start()
递归锁
import threading, time
lock = threading.RLock() # 递归锁,也叫重入锁,包含计数器,所以可以多次加锁解锁
# 面试官
def foo1():
lock.acquire() # 上锁,面试官等待的资源,
print("请解释什么是死锁")
time.sleep(1)
lock.acquire() # 上锁,面试官的资源
print("发offer")
time.sleep(1)
lock.release()
lock.release()
# 小明
def foo2():
lock.acquire() # 上锁,小明等待的资源
print("请给我发offer")
time.sleep(1)
lock.acquire() # 上锁,小明的资源
print("向面试官解释什么是死锁")
time.sleep(1)
lock.release()
lock.release()
t1 = threading.Thread(target=foo1)
t2 = threading.Thread(target=foo2)
t1.start()
t2.start()
'''
GIL 全局解释器
在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少个核
同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL的全程是全局解释器,来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以
把GIL看做是“通行证”,并且在一个python进程之中,GIL只有一个。拿不到线程的通行证,并且在一个python进程中,GIL只有一个,
拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操
作cpu,而只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的
python在使用多线程的时候,调用的是c语言的原生过程。
'''
'''
python针对不同类型的代码执行效率也是不同的
1、CPU密集型代码(各种循环处理、计算等),在这种情况下,由于计算工作多,ticks技术很快就会达到阀值,然后出发GIL的
释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等设计文件读写操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,
造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序的执行
效率)。所以python的多线程对IO密集型代码比较友好。
'''
'''
主要要看任务的类型,我们把任务分为I/O密集型和计算密集型,而多线程在切换中又分为I/O切换和时间切换。如果任务属于是I/O密集型,
若不采用多线程,我们在进行I/O操作时,势必要等待前面一个I/O任务完成后面的I/O任务才能进行,在这个等待的过程中,CPU处于等待
状态,这时如果采用多线程的话,刚好可以切换到进行另一个I/O任务。这样就刚好可以充分利用CPU避免CPU处于闲置状态,提高效率。但是
如果多线程任务都是计算型,CPU会一直在进行工作,直到一定的时间后采取多线程时间切换的方式进行切换线程,此时CPU一直处于工作状态,
此种情况下并不能提高性能,相反在切换多线程任务时,可能还会造成时间和资源的浪费,导致效能下降。这就是造成上面两种多线程结果不能的解释。
结论:I/O密集型任务,建议采取多线程,还可以采用多进程+协程的方式(例如:爬虫多采用多线程处理爬取的数据);对于计算密集型任务,python此时就不适用了。
'''
————————————————
版权声明:本文为CSDN博主「笨小孩哈哈」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40481076/article/details/101594705