python多线程异步(一)

一直想写一个多线程博客,汇总一下方老师教给我们的知识。但是因为一直没有用到,或者自己还没有吃透里面的精髓,所以不敢下笔。现在工作中又遇到必须要通过多线程解决的问题,所以再回顾以前方老师的课程,从头整理一下多线程异步这块知识,方便以后遇到问题可以快速写出代码来。

1、多线程异步初步介绍

串行和异步模式如下图,从图上可以很直观看出串行变成和异步编程区别
在这里插入图片描述
python 中线程需要用到自带的Threading包,可以使用并发

1.1一般的异步demo

import time
import threading

def syn_method():#串行的普通编程方式
    print("Start")

    time.sleep(1)
    print("Visit website 1")

    time.sleep(1)
    print("Visit website 2")

    time.sleep(1)
    print("Visit website 3")

    print("End")

def asyn_method():#异步多线程方式
    print("Start")

    def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的
        time.sleep(1)
        print("Visit website 1")

    def visit2():
        time.sleep(1)
        print("Visit website 2")

    def visit3():
        time.sleep(1)
        print("Visit website 3")

    th1 = threading.Thread(target=visit1)#首先定义多线程
    th2 = threading.Thread(target=visit2)
    th3 = threading.Thread(target=visit3)

    th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码
    th2.start()
    th3.start()

    th1.join()#最后汇总,等待线程1完成
    th2.join()
    th3.join()

    print("End")

asyn_method()#异步多线程函数
# syn_method()#同步串行函数

上面这个代码包含了线程定义、线程开始、线程阻塞执行三个要点。

在这里插入图片描述
在这里插入图片描述

1.2傀儡线程

在异步执行的时候,主线程执行完毕后(子线程没有join等待完成),有些子线程可能还没有启动,这时候就需要在主线程执行完毕后把所有没用启动或者正在执行的线程都杀死,避免造成系统垃圾。这时候就要用到傀儡线程,如果设置了傀儡线程,当主线程执行完毕后傀儡线程会自动关闭
傀儡线程的设置很简单,只需要在设置线程的时候增加daemon=True即可
把上面代码做了一下改进,1、把join()阻塞代码注释掉;2、在定义线程的时候增加daemon=True;3把sleep时间增加到10秒,便于更好的实验观察
代码如下:

import time
import threading

def syn_method():#串行的普通编程方式
    print("Start")

    time.sleep(1)
    print("Visit website 1")

    time.sleep(1)
    print("Visit website 2")

    time.sleep(1)
    print("Visit website 3")

    print("End")

def asyn_method():#异步多线程方式
    print("Start")

    def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的
        time.sleep(10)
        print("Visit website 1")

    def visit2():
        time.sleep(10)
        print("Visit website 2")

    def visit3():
        time.sleep(10)
        print("Visit website 3")

    th1 = threading.Thread(target=visit1,daemon=True)#设置daemon=True,把线程定义成傀儡线程,当主线程结束后,傀儡线程自动关闭,而不会在后台运行
    th2 = threading.Thread(target=visit2,daemon=True)
    th3 = threading.Thread(target=visit3,daemon=True)

    th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码
    th2.start()
    th3.start()

    #th1.join()#最后汇总,等待线程1完成
    #th2.join()
    #th3.join()

    print("End")

asyn_method()#异步多线程函数
# syn_method()#同步串行函数

增加了傀儡设置后,运行结果如下
在这里插入图片描述
各个子线程的结果没用输出,因为在等待的过程中主线程已经执行完毕了,各个子线程被杀死了。
实验二:把傀儡线程代码daemon=True删掉,或者改为False,结果如下
在这里插入图片描述
主线程执行完,但是程序并没有真正结束,等待一会儿子线程结果输出了,程序结束。
在这里插入图片描述

在这里插入图片描述

2、线程锁

2.1、为什么要锁

为什么要线程锁,只要有线程定义、线程开始、线程阻塞执行三个要点就够了吗?答案是肯定不够的
我们来看一个demo
该demo功能是一个多线程累加和一个多线程累减,
按照上面已经学到的知识可以很轻松写出下面代码

2.2、不加锁代码

import threading

n=0
def add(num):#累加的函数
    global n
    for _ in range(num):
        n+=1

def sub(num):#减的函数
    global n
    for _ in range(num):
        n -= 1

num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))

th_add.start()#启动线程
th_sub.start()

th_add.join()#阻塞执行
th_sub.join()

print("n=",n)
print("End")

简单分析一下,定义了一个多线程的加函数,又定义了一个多线程减函数,分别启动后阻塞等待程序执行完,不管输入的数字是多少,期待的结果总是为0才对,执行完毕后,结果如下:
在这里插入图片描述
n为啥不等于0,多换几个数字,依然不等于0
原因分析:
在这里插入图片描述
假设n=100的时候,首先加法线程取出100,然后进行加一操作等于101,但是这时候异步在执行,没有等把101结果返回给n的时候减法函数从内存中取出了n值为100,同样进行了减1操作,结果等于99,这时候99的结果和101结果同时赋值给n,所以这时候n的结果不是99就是101,但是不管结果是99或者101,这个时候n值已经错了(n值原来为100,加了一个数,然后又减了一个数,结果应该还是100才对)。
所以这时候就要用到线程锁来协调数据,在进行加法或者减法操作的时候希望n值是计算完成,写进去了,这样就不会乱了。
先在开头定义一把锁:lock = threading.Lock()。加锁有两种写法,可以用 lock.acquire()加锁,lock.release()解锁方法;或者直接with lock方法。
下面demo分别演示了两种加锁方式

2.3、加锁代码

import threading
lock = threading.Lock()#在开头定义一把锁
n=0
def add(num):#加的函数
    global n
    for i in range(num):
        lock.acquire()#加锁方法一。相当于java 的 lock.lock()
        try:
            n+=1
        finally:
            lock.release()#解锁

def sub(num):#减的函数
    global n
    for _ in range(num):
        with lock:#加锁方法二
            n -= 1

num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))

th_add.start()#启动线程
th_sub.start()

th_add.join()#阻塞执行
th_sub.join()

print("n=",n)
print("End")

不管num是多少,结果为0。符合预期结果
在这里插入图片描述

3、条件锁

有锁就够了吗,感觉还不够方便。因为如果我有一个缓冲区(缓冲区可以是个有限长度的列表,也类似于一个内存,内存容量是有限的,缓冲区的容量同样是指有限容量的空间),要在缓冲区内想利用多线程写进数据,同时也想从缓冲区获取数据,获取完数据就删除数据,释放缓冲区空间。缓冲区有大小,所以如果已经满了就写不进去,如果缓冲区没用数据,则获取失败。该问题就是生产者和消费者问题,或者也叫缓冲区问题。这时候就要用到条件锁
条件锁的定义方法:
has_data = threading.Condition(lock)#里面要传入一个锁,构成条件锁
has_loc= threading.Condition(lock)
一个lock可以有很多Condition,一个Condition只有一个lock。
Condition里面有个wait方法进行条件阻塞,配合if可以实现带条件的锁定和解锁。
阻塞的wait方法返回条件有两个,一是激活notify_all;二是timeout时间到了;
例如下面一个样例:1个lock,赋给了两个Condition,一个是C1,一个是C2;C1下面又有两个线程与其有关th1、th2;C2下面又有两个线程与其有关th3、th4、th5。如果这时候notify_all C1,则下面的th1和th2都活过来了。如果这时候notify_all C2,则th3、th4、th5都活过来了。
在这里插入图片描述

3.1、条件锁代码

下面看看代码

#程序功能:生产者消费者问题
import threading

class Buffer:#定义缓冲区类
    def __init__(self,buffer_size:int):
        self.buffer_size = buffer_size#缓冲区大小
        self.buffer = []#用一个列表模拟缓冲区
        lock = threading.RLock()#RLock允许在lock中进行第二次调用acquire()进行锁定,而如果是普通lock,不允许在lock中套入lock的acquire(),这样造成锁死(简单的说:一个锁把带有钥匙的主人给锁住了)。所以一般的RLock比较常用,这种lock相当于java ReentrantLock
        self.has_lock = threading.Condition(lock)#条件锁定义,表示有空位
        self.has_data = threading.Condition(lock)#条件锁定义,表示有数据


    def put(self,data,blocking = True):#向buffer传入数据,生产者。blocking是判定有没有缓存空间写入数据
        #return True if the data is put into this buffer successfully,False otherwise
        with self.has_lock:
            if blocking:
                while len(self.buffer)>=self.buffer_size:#条件锁的条件
                    self.has_lock.wait()#阻塞has_lock
            else:
                if len(self.buffer) >= self.buffer_size:
                    return False
            self.buffer.append(data)
            self.has_data.notify_all()#激活has_data条件锁
            return True



    def get(self,blocking = True):#取数据(消费者)
        with self.has_data:
            if blocking:
                while len(self.buffer)==0:#取数据等待的条件是缓冲区没用数据
                    self.has_data.wait()#阻塞has_data
            else:
                if len(self.buffer)==0:
                    return False
            result = self.buffer[0]
            del self.buffer[0]
            self.has_lock.notify_all()#删除了取出的数据,说明有空位了,这时候激活has_lock
            return result

if __name__ == '__main__':
    num = 20
    buffer= Buffer(5)
    def produce(n:int):#生产函数
        for i in range(num):
            data = "data_%d_%d"%(n,i)
            buffer.put(data)
            print(data,"is produced.")

    def resume():#消费函数
        for _ in range(num):
            data = buffer.get()
            print(data," is resume.")

    th0 = threading.Thread(target=produce,args=(0,))#定义一个多线程,调用生产的函数
    th1 = threading.Thread(target=produce,args=(1,))
    th2 = threading.Thread(target=resume)#定义一个多线程,调用消费的函数
    th3 = threading.Thread(target=resume)
    th0.start()
    th1.start()
    th2.start()
    th3.start()
    th0.join()
    th1.join()
    th2.join()
    th3.join()
    print("The test is End")

就这样吧,后面再在开新文章,增加几个多线程案例。

  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 库是否可行? 可以回答这个问题。Python 多线程调用异步库是可行的,但需要注意线程安全问题。异步库本身是为了解决 I/O 密集型任务而设计的,多线程可以提高 CPU 密集型任务的效率,但需要注意避免多线程之间的资源竞争和死锁等问题。建议使用 Python 的协程库 asyncio 来实现异步编程,同时使用 threading 或 multiprocessing 来实现多线程或多进程。 ### 回答2: Python中可以通过多线程调用异步操作来提高程序的并发能力和响应速度。 在Python中,可以使用`threading`模块来创建和管理多线程多线程是一种并发执行的方式,它允许多个线程在同一时间内执行不同的任务。 同时,Python也支持异步编程,可以通过`asyncio`模块来进行异步操作。异步操作可以在等待某些IO操作完成时,让出CPU资源给其他任务,以提高效率。 我们可以将异步操作放在一个线程中进行执行。具体步骤如下: 1. 使用`threading`模块创建一个线程对象,并指定一个函数为线程的执行体。 2. 在该函数中,使用`asyncio`模块的相关方法来定义和执行异步操作,比如使用`async`关键字定义异步函数,使用`await`关键字阻塞等待异步操作的完成。 3. 启动线程,开始执行异步操作。 这样,我们就可以在一个线程中同时执行多个异步操作,提高程序的并发能力。 需要注意的是,由于Python的全局解释锁(GIL)的存在,使得多线程无法真正实现并行执行。因此,通过多线程调用异步来提高程序的性能,主要是通过异步操作的非阻塞特性来实现任务的并发执行,而不是真正的并行执行。 总结起来,Python通过多线程调用异步可以提高程序的并发能力和响应速度,但需要注意多线程无法真正实现并行执行的限制。 ### 回答3: Python中的多线程调用异步可以通过一些库和技巧来实现。 首先,要注意Python的全局解释器锁(GIL),它会限制同一时间只能有一个线程在解释器中执行Python字节码。因此,Python多线程并不能真正实现并行计算,但可以用于处理I/O密集型任务。 要在Python中实现多线程调用异步,可以使用以下的方式之一: 1. asyncio库:asyncio是Python异步I/O框架,它使用协程来实现异步编程。可以使用asyncio库在多个线程中调用异步函数。需要注意的是,在使用asyncio时,应该确保所有的I/O操作都是非阻塞的,否则会阻塞主线程。 2. 多进程 + 异步库:Python中也可以通过多进程来实现并行的异步编程。可以使用multiprocessing库来创建多个进程,然后在每个进程中使用异步库来处理异步任务。 3. 线程池 + 异步库:Python中的concurrent.futures库提供了线程池和进程池的实现,可以通过线程池来实现多线程调用异步。可以使用ThreadPoolExecutor类来创建线程池,并将异步任务提交给线程池处理。 无论采用哪种方式,多线程调用异步都需要注意线程安全和资源共享的问题。需要使用锁和同步机制来保证线程安全,并避免竞态条件和死锁的发生。 综上所述,Python多线程调用异步可以使用asyncio库、多进程和异步库、线程池和异步库等方式实现。具体选择哪种方式取决于实际需求和场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值