【Python多线程编程——threading模块】—— Python多线程系列文章(二)


前言

上一篇文章 threading模块简介传送门:

https://blog.csdn.net/youngwyj/article/details/124720041

线程同步是多线程中很重要的概念,当多个线程需要共享数据时,如果不使用线程同步,就会存在数据不同步的情况。
要做到线程同步有两种方法,线程锁和条件变量Condition。


一、线程锁

1.Lock锁

threading模块中Lock锁和_thread模块中的锁是一样的。
示例:

import threading
import time

num = 0
lock = threading.Lock()  #申请线程锁

class threadTest(threading.Thread):  #类必须继承threading.Thread
    
    def __init__(self) -> None:  #args为传入线程的参数,可根据自己的需求进行定义
        
        super(threadTest,self).__init__()  #初始化super()内的必须与类名一样

    def run(self) -> None:  #定义run()方法,主要写线程的执行内容

        global num  #声明全局变量num

        # lock.acquire()  #申请线程锁

        print('子线程' + self.getName() + '开始:' + str(time.time()))
        while num < 5:
            time.sleep(2)  
            print(self.getName(),'num:',num)
            num += 1
        #休眠2s
        print('子线程' + self.getName() + '结束:' + str(time.time()))

        # lock.release()  #释放线程锁
        
        return super().run()

if __name__ == '__main__':

    print('主线程开始:%s'%(str(time.time())))
    thread1 = threadTest()
    thread1.setName('Thread-1') #设置线程名称
    thread2 = threadTest()
    thread2.setName('Thread-2') #设置线程名称    
    thread1.start()  #启动线程
    thread2.start()  #启动线程
    time.sleep(1)
    thread1.join()
    thread2.join()
    print('主线程已结束:%s'%(str(time.time())))

运行结果:

主线程开始:1652803641.5234373
子线程Thread-1开始:1652803641.5236645
子线程Thread-2开始:1652803641.5238614
Thread-2 num: 0
Thread-1 num: 0
Thread-2 num: 2
Thread-1 num: 3
Thread-2 num: 4
子线程Thread-2结束:1652803647.5305896
Thread-1 num: 5
子线程Thread-1结束:1652803647.5308123
主线程已结束:1652803647.531019

可以看到在未使用锁线程时,线程1和线程2对num操作出现了混乱。
将上面代码 lock.acquire() lock.release()这两行代码的注释去掉,将线程锁添加。
运行结果如下:可以看到不会像上面出现混乱的情况。

主线程开始:1652804037.7551372
子线程Thread-1开始:1652804037.7553797
Thread-1 num: 0
Thread-1 num: 1
Thread-1 num: 2
Thread-1 num: 3
Thread-1 num: 4
子线程Thread-1结束:1652804047.7664511
子线程Thread-2开始:1652804047.766612
子线程Thread-2结束:1652804047.7667737
主线程已结束:1652804047.7669005

2.RLock锁

RLock锁又称递归锁,其与Lock锁的差别在于,Lock锁只允许在同一线程中申请一次,否则线程会进入死锁,但是RLock允许在同一线程多次调用。
使用Lock锁产生死锁示例代码:

import threading
import time

print('主线程开始:%s'%(str(time.time())))
lock = threading.Lock()

lock.acquire()    #申请线程锁
print(threading.enumerate())

lock.acquire()    #再次申请线程锁,产生了死锁
print(threading.enumerate())

lock.release()
lock.release()
print('主线程结束:%s'%(str(time.time())))

运行结果:

主线程开始:1652804393.5089679
[<_MainThread(MainThread, started 140384603543360)>]
^Z					#此处为我在终端中主动杀死了线程,并非程序自己结束
[1]+  已停止

使用RLock锁不会产生死锁示例代码:

import threading
import time

print('主线程开始:%s'%(str(time.time())))
lock = threading.RLock()

lock.acquire()    #申请线程锁
print(threading.enumerate())

lock.acquire()    #再次申请线程锁,不会产生死锁
print(threading.enumerate())

lock.release()
lock.release()
print('主线程结束:%s'%(str(time.time())))

运行结果:

主线程开始:1652804609.3881009
[<_MainThread(MainThread, started 140639696934720)>]
[<_MainThread(MainThread, started 140639696934720)>]
主线程结束:1652804609.3881621

从上面可以看到Lock与RLock的区别
注意线程锁需要成对出现

二、条件变量 Condition

Condition是python3中一种更高级的锁,除和线程锁类似的 acquire() 和 release() 函数外,还提供以下函数。

函数 说明
wait() 使线程挂起
notify() 唤醒挂起的线程使其运行
notifyAll() 唤醒所有线程使其运行

注意:线程使用前需要获得锁,否则会抛出RuntimeError异常

可以理解为,Condition提供了一种多线程通信机制,若线程1需要数据,线程1就会阻塞等待,线程2制造出数据,等待线程2制造好数据并通知线程1后,线程1就可以去获取数据了

下面是一个使用条件变量 Condition 模拟成语接龙示例代码:

import threading
import time

class Man1(threading.Thread):  #类必须继承threading.Thread
    
    def __init__(self,lock) -> None:  #args为传入线程的参数,可根据自己的需求进行定义
        
        super(Man1,self).__init__()  #初始化super()内的必须与类名一样
        self.lock = lock

    def run(self):
        self.lock.acquire()  #申请锁
        print('子线程' + self.getName() + '为所欲为')

        self.lock.wait()   #挂起线程,等待回答
        print('子线程' + self.getName() + '逼上梁山')
        self.lock.notify()  #运行挂起的线程

        self.lock.wait()
        print('子线程' + self.getName() + '尽力而为')
        self.lock.notify()

        self.lock.release()

class Man2(threading.Thread):  #类必须继承threading.Thread
    
    def __init__(self,lock) -> None:  #args为传入线程的参数,可根据自己的需求进行定义
        
        super(Man2,self).__init__()  #初始化super()内的必须与类名一样
        self.lock = lock

    def run(self):
        self.lock.acquire()

        self.lock.notify()   #唤醒对方线程
        print('子线程' + self.getName() + '为法自弊')
        self.lock.wait()   #挂起线程,等待回答
        
        self.lock.notify()  
        print('子线程' + self.getName() + '山穷水尽')
        self.lock.wait()
        
        self.lock.notify()       
        print('子线程' + self.getName() + '为所欲为')

        self.lock.release()

if __name__ == '__main__':
    lock = threading.Condition()
    man1 = Man1(lock)
    man2 = Man2(lock)
    man1.setName('Thread-1') #设置线程名称
    man2.setName('Thread-2') #设置线程名称   
    print('成语接龙开始:')
    man1.start()
    man2.start()

运行结果:

成语接龙开始:
子线程Thread-1为所欲为
子线程Thread-2为法自弊
子线程Thread-1逼上梁山
子线程Thread-2山穷水尽
子线程Thread-1尽力而为
子线程Thread-2为所欲为

可以看到在条件变量的控制下,两个线程按照顺序执行,直到结束

猜你喜欢

转载自blog.csdn.net/youngwyj/article/details/124833126
今日推荐