theading库
python中有3种使用线程的方式,分别是thread、threading和ThreadPoolExecutor
Thread是比较低级的库,在python3中已经被废弃并重命名为_thread。
threading基于_thread构建,提供更完全的线程管理能力。
ThreadPoolExecutor在threading进一步封装,使得线程的使用进一步简化,但简化带来的问题就是ThreadPoolExecutor无法对创建的线程进行细粒度的控制。
threading有以下2种创建创建线程的方法
- 方法1:创建threading.Thread实例,将需要被线程执行的函数传入该实例。
- 方法2:创建一个类,该类继承于threading.Thread,重写其run方法。
方法1
import time import threading def longtime(n): time.sleep(n) def start(): t = threading.Thread(target=longtime,args=[10]) t.start() t.join() print('done') if __name__ == '__main__': start()
通过threading.Thread实例化线程对象并将需要线程执行的函数longtime传递给target参数,longtime函数对应的参数传递给args参数
其中
start()用于启动线程,此时线程就开始执行了,如果在同一个线程对象中多次调用会引发Runtimerr错误
join(timeout=none)方法会将主线程挂起,直到子线程运行结束,若timeout不为none,则表示主线程最长挂起时间,主线程结束挂起后,就会继续执行。
但是更多使用的是方法2以继承方式来创建线程。
import time import threading def longtime(n): time.sleep(n) class Mythread(threading.Thread): def __init__(self,func,args): #调用父类构造函数 super(Mythread, self).__init__() self.func= func self.args = args #线程执行的具体逻辑 def run(self) -> None: self.func(*self.args) def start(): #实例化线程 t = Mythread(longtime,(10,)) t.start() t.join() print('done')
通过run方法的方式,可以自定义线程具体的执行逻辑,相比方法1,这种方法更加灵活直观,比如在run()方法中加一项逻辑,对传入方法做一些额外的逻辑处理等
线程锁
threading库提供了Lock、RLock、Semaphore、Condition、Event及Queue()等多种线程同步机制,下面使用一下这些同步机制
如果没有采用一些同步机制,多个线程使用同一个资源时就容易产生意料之外的情况。下面使用2个线程将1.txt和2.txt这2个文件按顺序写入同一个文件中。
import time import threading class Mythreading(threading.Thread): def __init__(self,innt,output): super(Mythreading, self).__init__() self.innt = innt self.output = output #传入的lock对象 def run(self) -> None: for line in self.innt.readlines(): time.sleep(1)#模拟耗时操作 self.output.write(line) print('线程结束') def main(): txt1 = open('1.txt','r',encoding='utf-8') txt2 = open('2.txt','r',encoding='utf-8') txt3 = open('3.txt','a',encoding='utf-8') t1 = Mythreading(txt1,txt2) t2 = Mythreading(txt2,txt3) t1.start() t2.start() print('done') main()
通过继承的方式构建了MYthread类并将线程要执行的逻辑直接写入run()中,创建于2个线程将不同文件的内容写入同一文件中。
此时3.txt内容必然是混乱的。
要避免这种情况,最简单的就是利用锁lock
import time import threading class Mythreading(threading.Thread): def __init__(self,innt,output,lock): super(Mythreading, self).__init__() self.innt = innt self.output = output self.lock = lock #传入的lock对象 def run(self) -> None: self.lock.acquire()#获得lock对象,lock变为被锁状态,并且阻塞其他线程获取lock对象 for line in self.innt.readlines(): time.sleep(1)#模拟耗时操作 self.output.write(line) self.lock.release()#释放lock对象 print('线程结束') def main(): lock =threading.Lock() txt1 = open('1.txt','r',encoding='utf-8') txt2 = open('2.txt','r',encoding='utf-8') txt3 = open('3.txt','a',encoding='utf-8') t1 = Mythreading(txt1,txt2,lock) t2 = Mythreading(txt2,txt3,lock) t1.start() t2.start() print('done') main()
threading.Lock()创建对象,通过acquire()方法获得锁,通过release()释放锁,没有获取锁的线程只能等待,通过这种简单方式,避免了3.txt内容混乱问题。
此外,可以利用wirh关键词简化锁的获取与释放过程
with self.lock:#锁会自动获取和释放 for line in self.innt.readlines(): time.sleep(1) # 模拟耗时操作 self.output.write(line) print('线程结束')
简单总结,线程获取锁后有locked(被锁)与unlocked(未被锁)2种状态,分别对应acquire()与release()方法,没有获取锁的线程无法执行,通过这种方式可实现同一时刻下有且只有一个线程在运行的目的
需要注意,线程在locked状态再次调用acquire()方法会产生死锁,如果想在同一线程下多次使用acquire()方法,需要使用可重入锁。
可重入锁
与普通锁不同,可重入锁底层使用递归实现,同一个线程每次调用acquire()方法获得锁,对应的计数器会加1,而调用release()方法释放锁时,计数器加1.
可重入锁要求调用acquire()方法的次数与调用release()方法次数相同
可重入锁的使用与普通锁使用除了lock对象的实例化外完全相同