假设有两个线程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 in %d---' % g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print('---in work2,g_num in %d---' % g_num)
print('---线程创建之前g_num: %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('2个线程对同一个变量操作之后的最终结果:%d' % g_num)
---线程创建之前g_num: 0
---in work1,g_num in 10---
---in work2,g_num in 20---
2个线程对同一个变量操作之后的最终结果:20
情况1:
在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。
情况2:
如果我们将两个线程的参数调整为1000000,多次运行,结果不同。
说明如果多线程如果同时对一个全局变量操作,会出现一个资源竞争的问题,从而使数据结果不准确,导致线程安全问题。
同步,就是协同步调。按照预定的先后次序进行运行,好比交流,一个说完,另一个人再说。
进程和线程同步,可以理解为进程或者线程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]) #锁定
mutex.release() #释放
说明:
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
if mutex.acquire(True):
g_num += 1
mutex.release()
print('---in work1,g_num in %d---' % g_num)
def work2(num):
global g_num
for i in range(num):
if mutex.acquire(True):
g_num += 1
mutex.release()
print('---in work2,g_num in %d---' % g_num)
print('---线程创建之前g_num: %d' % g_num)
mutex = threading.Lock()
t1 = threading.Thread(target=work1, args=(1000000,))
t2 = threading.Thread(target=work2, args=(1000000,))
t1.start()
time.sleep(1)
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print('2个线程对同一个变量操作之后的最终结果:%d' % g_num)
g_num: 0
---in work1,g_num in 1000000---
---in work2,g_num in 2000000---
2个线程对同一个变量操作之后的最终结果:2000000
在线程共享多个资源时,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁
死锁一般很少发生,但一旦发生就会造成停止响应。
import threading
import time
printer_mutex = threading.Lock() # 打印机锁
paper_mutext = threading.Lock() # 纸张锁
class ResumeThread(threading.Thread):
"""编写个人简历任务的线程"""
def run(self):
print("ResumeThread:编写个人简历任务")
# 使用打印机资源,先对打印机加锁
printer_mutex.acquire()
print("--ResumeThread:正在使用打印机资源--")
time.sleep(1) # 休眠1秒
# 使用纸张耗材,先对纸张耗材加锁
paper_mutext.acquire()
print("--正在使用纸张资源--")
time.sleep(1)
paper_mutext.release() # 释放纸张锁
# 释放打印机锁
printer_mutex.release()
class PaperListThread(threading.Thread):
"""盘点纸张耗材任务的线程"""
def run(self):
print("PaperListThread:盘点纸张耗材任务")
# 使用纸张耗材,先对纸张耗材加锁
paper_mutext.acquire()
print("--PaperListThread:正在盘点纸张耗材--")
time.sleep(1) # 休眠1秒
# 使用打印机资源,打印清单
printer_mutex.acquire()
print("--正在使用打印机资源--")
time.sleep(1)
printer_mutex.release() # 释放打印机锁
# 释放纸张耗材锁
paper_mutext.release()
if __name__ == '__main__':
t1 = ResumeThread()
t2 = PaperListThread()
t1.start()
t2.start()
ResumeThread:编写个人简历任务
--ResumeThread:正在使用打印机资源--
PaperListThread:盘点纸张耗材任务
--PaperListThread:正在盘点纸张耗材--
同步的应用
让多个线程有序的执行
import threading
import time
class Task1(threading.Thread):
def run(self):
while True:
if lock1.acquire():
print('---Task1---')
time.sleep(0.5)
lock2.release()
class Task2(threading.Thread):
def run(self):
while True:
if lock2.acquire():
print('---Task2---')
time.sleep(0.5)
lock3.release()
class Task3(threading.Thread):
def run(self):
while True:
if lock3.acquire():
print('---Task3---')
time.sleep(0.5)
lock1.release()
# 使用Lock创建锁,默认没有锁上
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---
---Task2---
---Task3---
---Task1---
生产者消费者的问题
也就是有限缓冲问题,是一个多线程同步的经典案例
描述了一个两个固定大小缓冲区的线程——即所谓的’生产者’和’消费者’——在实际运行时会发生的问题
生产者的主要作用,生成一定数量的数据放在缓冲区中,然后,重复此过程
于此同时,消费者也在缓冲区消耗这些数据
整个问题的关键是,生产者不会在缓冲区满时假如数据,消费者也不会在缓冲区空时消耗数据
解决办法
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形
1.队列,先进后出
2.栈,先进后出
Python 中queue(py3)(py2,Queue,)模块提供了一个同步的、线程安全的队列类,包括先入先出(FIFO)队列queue,和后人先出(LIFO),队列LifoQueue和优先级队列PriorityQueue
这些队列实现了锁原语(原子操作,要么不做,要么做完),可以在线程中直接使用
可以使用队列来实现线程间的同步
FIFO队列实现生产者消费者的问题
import threading
import time
from queue import Queue
class Producer(threading.Thread):
def run(self):
global queue
count = 0
while True:
if queue.qsize() < 1000:
for i in range(100):
count = count +1
msg = '生成产品'+str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + '消费了 '+queue.get()
print(msg)
time.sleep(1)
if __name__ == '__main__':
queue = Queue()
for i in range(500):
queue.put('初始产品'+str(i))
for i in range(2):
p = Producer()
p.start()
for i in range(5):
c = Consumer()
c.start()