第37天并发编程之线程篇 (2)

#: 当我们打印数据的时候发现结果是99,为什么呢?
#: 因为每个线程temp = x然后等待i/o,当cpu再次切换过来执行的时候temp=100,x的值就是99
print(x)
因此在一些数据的交互过程中我们需要加上线程锁来保证数据的安全性
from threading import Thread, Lock
import time

x = 100
mutex = Lock() # 创建锁

def task():
global x
mutex.acquire() # 在数据修改之前添加锁
temp = x
time.sleep(0.1)
x = temp - 1
mutex.release() # 在数据修改之后释放锁

#: 创建100个线程全局变量x进行修改,每次减1
t_l = []
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()

# 用来等待所有线程结束
for i in t_l:
i.join()

print(x)

死锁
死锁的现象就是两个线程(或者两个进程)A和B,还有两个互斥锁C和D。A和B都需要C和D这两把锁,但是A抢到了C却没有抢到B,而B抢到了D却没有抢到C,从而导致程序进入死锁状态。
from threading import Thread, Lock, current_thread

mutex1 = Lock()
mutex2 = Lock()

def task1():
mutex1.acquire()
print('%s 抢到了锁1' % current_thread().name)
# 当抢到锁1之后,cpu就会执行其他的程序
# 当再回来的时候却发现锁2被task2抢到了因此在等待task2释放锁2
# 但是此时task2和task1是一样的,在等待task1释放锁1,此时就进入了死锁的状态
time.sleep(0.1)
mutex2.acquire()
print('%s抢到了锁2' % current_thread().name)

mutex2.release() print('%s释放了锁2' % current_thread().name) mutex1.release() print('%s释放了锁1' % current_thread().name)

def task2():
mutex2.acquire()
print('%s 抢到了锁1' % current_thread().name)
time.sleep(0.1)
mutex1.acquire()
print('%s抢到了锁2' % current_thread().name)

mutex1.release() print('%s释放了锁2' % current_thread().name) mutex2.release() print('%s释放了锁1' % current_thread().name)

t1 = Thread(target=task1)
t2 = Thread(target=task2)
t1.start()
t2.start()
3.递归锁
递归锁所使用的是RLock函数,其原理是如果我自己需要多把锁的时候,我就把这多把锁设置成一个递归锁,抢到一次递归锁计数就加1,当其他的线程或者进程想使用这一把锁的时候,会首先去查看锁计数是否为0,如果不为零,就等待其他的进程或者线程来释放锁,这就可以解决死锁问题了。
# mutex1 = Lock()
# mutex2 = Lock()
# 只需要将上面的锁修改成递归锁就可以解决上面的死锁问题了
mutex1 = mutex2 = RLock()
4.信号量
信号量使用的是Semaphore类,我们可以给他传递一个值来代表一次可以同时运行几个线程或者是几个进程,类似于一种池的概念,在某种意义上它是不能够保证数据的安全性。只是说在某些没有数据交互的场景下我们可以控制其每次进程或者线程的数量。
from threading import Thread, Semaphore, current_thread
import time
import random

s = Semaphore(5)
x = 30
def go_wc():
global x
s.acquire()
temp = x
time.sleep(random.randint(1, 3))
x = temp - 1
s.release()

t_l = []
for i in range(30):
t = Thread(target=go_wc)
t_l.append(t)
t.start()

for i in t_l:
i.join()

print(x)
5.GIL全局解释器锁

python程序执行的三个步骤
(1). 开辟一块内存空间,启动Python解释器
(2). 加载python程序到内存中
(3). 将内存中的程序传递给python解释器一步一步执行

问题:为什么多个线程不能同时使用一个python解释器呢?
这是因为在Python中有一种垃圾回收机制,当一个value的引用计数为0之后,就会被python的垃圾回收机制所清空掉。但是python的垃圾回收机制其实也是通过一个线程来执行的,如果可以同时调用解释器,这就会出现这样一个问题:如果我赋值了一个操作a = [1, 2, 3]的时候,当我这个线程还没有执行这个操作,只是创建了一个值[1, 2, 3]的时候,突然python解释器把垃圾回收机制的线程给执行了,这是垃圾回收机制就会发现这个值[1, 2, 3]当前引用计数还是0呢,就直接清掉了,但是此时我还没有来得及给a赋值呢,这就出现了数据错乱的问题。
# This lock is necessary mainly because CPython’s memory management is not thread-safe.
# 意思是CPython的内存管理机制(垃圾回收机制)不是线程安全的,因此我们不能让python线程同时去调用python解释器。

什么叫做全局解释器锁
# In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
# native threads from executing Python bytecodes at once.
# GIL全局解释器锁是一种互斥锁,在同一时刻,保证多个线程中只能有一个线程使用python解释器

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfxjj.html