python多线程进阶

微信扫一扫,分享到朋友圈

python多线程进阶
收藏 00

theading库

python中有3种使用线程的方式,分别是thread、threading和ThreadPoolExecutor

Thread是比较低级的库,在python3中已经被废弃并重命名为_thread。

threading基于_thread构建,提供更完全的线程管理能力。

ThreadPoolExecutor在threading进一步封装,使得线程的使用进一步简化,但简化带来的问题就是ThreadPoolExecutor无法对创建的线程进行细粒度的控制。

threading有以下2种创建创建线程的方法

  1. 方法1:创建threading.Thread实例,将需要被线程执行的函数传入该实例。
  2. 方法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对象的实例化外完全相同

 

 

一个热爱互联网的咸鱼

你也可能喜欢

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片

热门

    抱歉,30天内未发布文章!
返回顶部