python-线程同步

概述

通过 python-多线程修改公共变量引发的血案 可见,多线程修改公共变量时,会引发bug,为了解决bug,要用到线程同步

在多线程修改公共变量时,如下图:

  1. 第一步,先让一个线程读变量num的值
    注意:具体是哪个线程不一定,由操作系统调度,图中由 线程1 执行
  2. 第二步,让该线程计算后将值写入内存
  3. 第三步,然后让另一个线程读变量num的值
  4. 第四步,让该线程计算后将值写入内存

临界资源

某些公共资源只能一个线程访问操作,这样的资源称为临界资源(互斥资源,共享变量)

如:上面的商品数量、票号,或者打印机

临界区

访问临界资源的那段代码称为临界区

如:上面访问商品数量、票号的代码

线程互斥

由于多线程要共享资源,而有些资源需要互斥使用,因此各线程间竞争使用这些资源

如:多线程修改公共变量,多线程访问打印机等

线程同步

多线程访问操作公共资源时,需要多个线程有顺序的执行,互相合作、互相等待,共同完成一项任务。

一般来说,同步往往包含互斥

使用互斥锁

为了实现多线程同步和互斥,python提出互斥锁
当一个线程申请互斥锁后,就只有该线程运行,其他线程都阻塞,直到该线程释放锁时,其他线程才会继续执行

由此可见,申请互斥锁和释放互斥锁之间的代码,只有一个线程在运行,所以互斥锁不要滥用,否则会导致多线程效率低下

Lock类的使用说明

threading模块中定义了Lock类,实现互斥锁,让线程同步
注意:当 多个线程 修改 公共变量时,才需要互斥锁

创建锁

lock=threading.Lock()

获得锁

表示之后的代码全部被锁定,只能一个线程执行

lock.acquire([blocking=True,timeout=-1])

参数:

  • blocking:为True,表示当前线程会阻塞,直到获得锁为止。
    False,当前线程不会阻塞,直接返回false
    默认为True
  • timeout:当blocking为True时有意义,表示n秒后返回false

释放锁

表示之后的代码,多线程都可以同时执行

lock.release()

改造之前的代码

#coding=utf-8
import threading

def modify():
    global num
    global lock
    #获得锁,之后的代码只能有一个线程执行,具体是哪个线程不一定,由os调度
    lock.acquire()
    t=threading.current_thread()
    #获取当前线程对象
    t=threading.current_thread()
    for index in range(100000):
        num+=1
        print('%s,num=%d'%(t.name,num))
    #释放锁,之后的代码可以多线同时执行
    lock.release()


if __name__ == "__main__":
    num=0
    #创建锁
    lock=threading.Lock()
    #创建线程,此线程修改全局变量
    t=threading.Thread(target=modify)
    #创建线程,此线程打印全局变量
    t2=threading.Thread(target=modify)
    #启动线程
    t.start()
    t2.start()
    #阻塞
    t.join()
    t2.join()
    print('执行结束,num=%d'%(num))

改造卖火车票代码

#coding=utf-8
import threading
import time
import os
#票总数
count=500
#窗口数量
windownum=3
#初始化票号码
num=1

def sell(index):
    global num,lock
    print('%d号售票口-开始售票,票号:%d'%(index,num))
    #修改变量

    while True:
        lock.acquire()
        if num<=count:#本行代码是临界区,要在上面加锁
            print('%d号售票口-售出票号:%d'%(index,num))
            i=num+1
            # time.sleep(0.00001)
            num=i
            print('%d号售票口-下一票号:%d'%(index,num))
            lock.release()
        else:
            print('%d号售票口-售罄'%(index))
            lock.release()
            break

if __name__ == "__main__":
    lock=threading.Lock()
    stime=time.time()
    ts=[]
    for index in range(windownum):
        t=threading.Thread(target=sell,args=(index,))
        ts.append(t)
    for item in ts:
        item.start()
    for index in ts:
        item.join()
    #主进程打印值
    print('主进程,num=%s'%num)
    etime=time.time()
    print(etime-stime)