多线程-共享全局变量
多线程可能遇到的问题
假设有两个线程t1和t2,都要对一个变量g_num进行运算(+1)。两个线程t1和t2分别对g_num各加10次,g_num的最终结果?
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num+=1
print('---in work1,g_num is %d---'%g_num)
def work2(num):
global g_num
for i in range(num):
g_num+=1
print('---in work2,g_num is %d---'%g_num)
print("----线程创建之前,g_gum is %d---"%g_num)
t1=threading.Thread(target=work1,args=(10,))
t2=threading.Thread(target=work2,args=(10,))
t1.start()
t2.start()
while len(threading.enumerate())!=1:
time.sleep(1)
print("----线程创建之后,g_gum is %d---"%g_num)
----线程创建之前,g_gum is 0---
---in work1,g_num is 10---
---in work2,g_num is 20---
----线程创建之后,g_gum is 20---
看起来没什么问题,但多线程时可能会发生以下可能:
在num=0时, t1取得num=0, 此时系统把t1调度为’sleeping’状态,t2转换为’running’的状态,t2也获得了num=0。然后,t2对得到的值进行加1,并赋给num, num=1。然后,系统又把t2调度为’sleeping’状态,t1转换为’running’的状态。线程t1又把它之前得到的0加1赋值给num。这种情况,明明两个线程都完成了一次+1工作,但结果还是num=1。
比如我们将两个进程的参数调整为1000000,多次运行,结果不同。
说明多个线程同时对一个全局变量进行操作,会出现资源竞争问题,从而数据结果会不正确,导致线程安全问题。
----线程创建之前,g_gum is 0---
---in work1,g_num is 1173635---
---in work2,g_num is 1420127---
----线程创建之后,g_gum is 1420127---
同步
同步,就是协同步骤。按照预定的先后次序进行运行,好比交流,一个人说完,另一人再说。
进程和线程同步,可以理解为进程或者线程A和线程B一块配合,A执行一定程序时需要依赖B的某个结果,于是停下来,让B运行,B开始运行,再将结果给A,A再继续操作。如此往复,直至程序结束。
计算错误的解决办法
通过线程同步进行解决
思路
- 系统调度t1,获取num=0,此时上一把锁,即不允许其他线程操作num
- 对num的值加1
- 解锁,此时num的值为1,其他的线程就可以使用num了,此时num=1
- 同理,其他线程在对num修改时,也要先上锁,处理完再解锁
互斥锁
当多个线程几乎同时修改某个共享数据时,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁。
互斥锁为我们的资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时,资源的状态为锁定,其他线程不能对其更改;直到该线程释放资源,资源状态变为"非锁定"状态,其他线程才能再次锁定该资源。
互斥锁,保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
在threading模块里,定义了Lock()类,可以方便的处理锁定。
mutex=threading.Lock()#创建
mutex.acquire([blocking])#锁定
mutexrelease()#释放
说明
- blocking
- True,则当前线程堵塞,直到获取这个锁为止(若未指定,则默认为True)
- False,则线程不会被堵塞
def work1(num):
global g_num
if mutex.acquire(True):
for i in range(num):
g_num+=1
mutex.release()
print('---in work1,g_num is %d---'%g_num)
def work2(num):
global g_num
if mutex.acquire(True):
for i in range(num):
g_num+=1
mutex.release()
print('---in work2,g_num is %d---'%g_num)
对上面的程序添加一个互斥锁,结果就正常了
----线程创建之前,g_gum is 0---
---in work1,g_num is 1000000---
---in work2,g_num is 2000000---
----线程创建之后,g_gum is 2000000---
死锁
在线程间共享多个资源时,如果一两个线程分别占有一部分资源,并且等待对方的资源,就会造成死锁。
死锁一般很少发生,但一旦发生就会造成应用停止响应。
同步的应用
让多个程序同时有序运行
import threading
import time
class Task1(threading.Thread):
def run(self):
while 1:
if lock1.acquire():
print("----Task1----")
time.sleep(0.5)
lock2.release()
class Task2(threading.Thread):
def run(self):
while 1:
if lock2.acquire():
print("----Task2----")
time.sleep(0.5)
lock3.release()
class Task3(threading.Thread):
def run(self):
while 1:
if lock3.acquire():
print("----Task3----")
time.sleep(0.5)
lock1.release()
#使用Lock创建锁1,默认没有锁上
lock1=threading.Lock()
#创建锁2,并且锁上
lock2=threading.Lock()
lock2.acquire()
#创建锁3,并且锁上
lock3=threading.Lock()
lock3.acquire()
t1=Task1()
t2=Task2()
t3=Task3()
t1.start()
t2.start()
t3.start()
---------------
----Task1----
----Task2----
----Task3----
----Task1----
----Task2----
----Task3----
----Task1----
......
生产者消费者问题
也就是有限缓冲问题,是一个多线程同步的经典案例。
描述了一个两个固定大小缓冲区的线程—即所谓的“生产者”和“消费者”—在实际运行时会发生的问题。
生产者的主要作用,生产一定量的数据放在缓冲区,然后,重复此过程。
与此同时,消费者也在缓冲区消耗这些数据。
然而问题关键是,生产者不会再缓冲区满时加入数据,消费者也不会再缓冲区空时消耗数据。
解决办法
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形
1.队列,先进先出
2.栈,先进后出
python中的Queue模块提供了一个同步的、线程安全的队列类,包括先入先出(FIFO)队列Queue,和后入先出(LIFO)队列LifoQueue和优先级队列PriorityQueue。这些队列实现了锁原语(原子操作,要么不做,要么做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。