每个CPU在同一时间只能执行一个线程,那么其他的线程就必须等待该线程的全局解释器,使用权消失后才能使用全局解释器,即使多个线程直接不会相互影响在同一个进程下也只有一个线程使用CPU,这样的机制称为全局解释器锁(GIL)。GIL的设计简化了CPython的实现,使得对象模型包括关键的内建类型,如:字典等,都是隐含的,可以并发访问的,锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
Python全局解释器锁(GIL)是一种互斥锁或锁,仅允许一个线程持有Python解释器的控制权。
全局解释器锁的好处
1、避免了大量的加锁解锁的好处
2、使数据更加安全,解决多线程间的数据完整性和状态同步。
全局解释器锁的劣势
多核处理器退化成单核处理器,只能并发不能并行。
Python全局解释器锁(GIL)的作用
多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
有很多的场景中的事情是同时进行的,比如开车的时候,手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的
结果:
• _thread
• threading(推荐使用)
结果:
threading.enumerate() 可查看当前正在运行的线程
结果:
结果:
结果:
结果: 出现资源竞争导致计算结果不正确
(1)当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
(2)线程同步能够保证多个线程安全访问资源,最简单的同步机制是引入互斥锁
(3)互斥锁为资源引入一个状态: 锁定/非锁定
(4)某个线程要更爱共享数据时,先将其锁定,此时资源的状态为"锁定", 其他线程不能更改直到该线程释放资源,将资源状态变为"非锁定"
(5)互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
结果: 计算正确
结果:卡住了
在线程间共享多个资源的时候,如果两个线程分别战友一部分资源且同时等待对方资源,就会造成死锁
(1)程序设计时避免(银行家算法)
(2)添加超时时间
同时开启三个线程,分别只能输出1,2,3,并让三个线程按顺序输出打印123123123…这道问题简单来说就是一种红绿灯,我们先让红灯亮,过一段时间后黄灯亮,再过一段时间后绿灯量,以此类推。一般对于多线程而言,可以很容易的实现这三种颜色的切换,但是再不对其进行控制的话,他的执行顺序将会是乱的,其可能在红灯之后还是红灯,绿灯之后还是绿灯(这要是在现实中将会引发巨大的灾难),因此要实现多线程之间的顺序执行,我们就需要使用到锁的概念,也就是Python中的GIL、同步锁(互斥锁)、递归锁(用来解决死锁,当有多个互斥锁存在的时候,可能会导致死锁),对于Python中锁的概念不进行阐述,我们在这里直接使用Threading.Lock来解决上述问题。
实现多线程的顺序执行,关键在于如何控制上锁lock.acquire()与锁的释放lock.release()。在这里我们可以将红绿灯的各种颜色的切换看作是上锁与释放锁。一开始是红灯亮,因此,在初始时刻对于红黄绿的三种锁中,只有红灯的锁是释放的,而黄灯和绿灯的锁是被锁着的,这样一来只有红灯的线程可以进入得到资源,从而显示为红灯,这里需要注意的是红灯的线程进入后,应将其锁给锁住,而不让其他线程进入(红灯亮着的时候,三个锁都是锁住的);当红灯显示时间结束后,下一个为黄灯,因此需要将黄灯的锁给释放掉,从而让黄灯的线程进入,进入后再将锁锁住;当黄灯显示完毕后,将绿灯的锁给打开,从而让绿灯显示,以此类推。
完整代码如下:
import threading
import time
red_lock = threading.Lock() # 红灯锁
yellow_lock = threading.Lock() # 黄灯锁
green_lock = threading.Lock()# 绿灯锁
count = 18 # 为避免一直循环,我们在这里假设每个数字输出6次,3×6=18
def red():
"""红灯显示函数"""
global count
while count >= 0:
red_lock.acquire() # 将红灯的锁给锁住
print(1, end = '-') # 将红灯表示为1
# print('id:', threading.get_ident()) # 查看线程id
yellow_lock.release() # 下一个为黄灯亮,将黄灯的锁给释放
count -= 1
def yellow():
"""黄灯显示函数"""
global count
while count >= 0:
yellow_lock.acquire() # 将黄灯的锁给锁住
print(2, end = '-') # 将黄灯表示为2
# print('id:', threading.get_ident())
green_lock.release()# 下一个为绿灯亮,将绿灯的锁给释放
count -= 1
def green():
"""绿灯显示函数"""
global count
while count >= 0:
green_lock.acquire()# 将绿灯的锁给锁住
print(3, end = '-') # 将绿灯表示为2
# print('id:', threading.get_ident())
red_lock.release() # 下一个为红灯亮,将红灯的锁给释放
count -= 1
if __name__ == '__main__':
thread_list = []
func_list = [red, yellow, green]
for func in func_list: # 创建三个线程
th = threading.Thread(target = func)
thread_list.append(th)
# 红灯先亮,因此将黄灯和绿灯的锁给锁住,以阻塞线程2和3的执行
yellow_lock.acquire() # 2上锁
green_lock.acquire() # 3上锁
for th in thread_list:
# print(time.time()) # 用于判断启动时间
th.start()
for th in thread_list:
th.join()
登录后复制
执行结果为:
1-2-3-1-2-3-1-2-3-1-2-3-1-2-3-1-2-3-1-2-3-
登录后复制
上面的判断启动时间的是用来看是否是同时启动的,由于for循环是有顺序的,因此三个线程的启动并不是同时的,虽然有些时候输出的时间会相同,但其实是不同的。