首页 > 编程语言 >【Python进阶】并发编程方式

【Python进阶】并发编程方式

时间:2024-02-03 11:22:08浏览次数:37  
标签:__ 进阶 Python 编程 worker started print 线程 进程

并发编程方式有哪些?

  • threading模块---线程
  • asyncio模块---协程
  • concurrent.futures模块---进程+线程(应用于异步调用)
  • multiprocessing模块---进程

进程、线程、协程?

  • 进程:运行起来的程序就是进程,是操作系统分配资源的最小单位。
  • 线程:线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
  • 协程:是线程的更小切分,又称为“微线程”,是一种用户态的轻量级线程。

三者关系:进程里有线程,线程里有协程

进程、线程、协程的区别

  • 进程:
    针对于python语言执行环境来说,多进程是利用多核CPU来完成任务,进程拥有独立的内存空间,所以进程间数据不共享,进程之间的通讯是由操作系统完成的,在切换时,CPU需要进行上下文切换,导致通讯效率比较低、开销比较大。
  • 线程:
    多线程是在一个进程内运行,共享进程的内存空间,通讯效率较高、开销较小。但缺点是:因为python底层的GIL锁(global interpeter lock),python的底层的同步锁太多导致多线程被同步很多次,搞成并发效果不佳。
  • 协程:
    一个可以挂起的函数,协程的调度完全由用户控制,用函数切换,开销极小。

多进程、多线程、协程的使用场景

  • 多进程:
    CPU密集运算,大部分时间花在计算
  • 多线程、协程:
    IO密集型(网络IO、磁盘IO、数据库IO),大部分时间花在传输

threading模块

Python中的threading模块提供了多线程编程的基本支持。使用该模块可以创建和管理线程,从而实现并发执行。

示例

import threading  
def worker():  
    print('Worker thread started')  
    # do some work here  
    print('Worker thread finished')  
if __name__ == '__main__':  
    print('Main thread started')  
    # create a new thread  
    t = threading.Thread(target=worker)  
    # start the new thread  
    t.start()  
    print('Main thread finished')  

执行结果:

Main thread started  
Worker thread started  
Main thread finished  
Worker thread finished  

asyncio模块

Python中的asyncio模块提供了异步编程的支持。使用该模块可以实现协程,从而在单线程中实现并发执行。

import asyncio  
async def worker():  
    print('Worker task started')  
    # do some work here  
    print('Worker task finished')  
if __name__ == '__main__':  
    print('Main task started')  
    # create a new event loop  
    loop = asyncio.get_event_loop()  
    # run the worker coroutine  
    loop.run_until_complete(worker())  
    # close the event loop  
    loop.close()  
    print('Main task finished')  

执行结果:

import asyncio  
async def worker():                      # 一个异步函数worker,该函数会在一个协程中执行。
    print('Worker task started')  
    # do some work here  
    print('Worker task finished')  
if __name__ == '__main__':  
    print('Main task started')  
    # create a new event loop  
    loop = asyncio.get_event_loop()      # 创建了一个新的事件循环loop
    # run the worker coroutine  
    loop.run_until_complete(worker())    # 调用run_until_complete方法来运行worker协程
    # close the event loop  
    loop.close()                         # 关闭了事件循环
    print('Main task finished')  

执行结果:

Main task started  
Worker task started  
Worker task finished  
Main task finished  

程序先执行了主任务中的代码,然后通过事件循环执行了worker协程。
协程是在单线程中执行的,因此程序的执行速度得到了提高。

concurrent.futures模块

concurrent.futures模块是Python3中的新模块,它提供了线程池和进程池的实现。使用该模块可以更方便地实现并行执行。

import concurrent.futures  
def worker():  
    print('Worker thread started')  
    # do some work here  
    print('Worker thread finished')  
if __name__ == '__main__':  
    print('Main thread started')  
    # 在主线程中创建了一个线程池executor,并设置最大线程数为2。接着,通过调用submit方法将worker函数提交给线程池。
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:  
        # submit worker function to the pool  
        future = executor.submit(worker)  
        print('Main thread finished')  

执行结果:

Main thread started  
Main thread finished  
Worker thread started  
Worker thread finished  

从上面的输出结果可以看出,程序先执行了主线程中的代码,然后通过线程池执行了worker函数。线程池会自动管理线程的创建和销毁,从而使程序更加高效。

multiprocessing模块

Python中的multiprocessing模块提供了多进程编程的支持。使用该模块可以在不同的进程中执行任务,从而实现并发执行。

import multiprocessing  
def worker():  
    print('Worker process started')  
    # do some work here  
    print('Worker process finished')  
if __name__ == '__main__':  
    print('Main process started')  
    # create a new process  
    p = multiprocessing.Process(target=worker)  
    # start the new process  
    p.start()  
    print('Main process finished')  

执行结果:

Main process started  
Main process finished  
Worker process started  
Worker process finished  

创建了一个新的进程,并在新进程中执行worker函数。
主进程和新进程是并行执行的,因此程序的执行速度得到了提高

multiprocessing.Process详解

Process([group [, target [, name [, args [, kwargs]]]]])

  • group分组,实际上不使用
  • target表示调用对象,你可以传入方法的名字
  • args表示给调用对象以元组的形式提供参数,比如target是函数a,他有两个参数m,n,那么该参数为args=(m, n)即可
  • kwargs表示调用对象的字典
  • name是别名,相当于给这个进程取一个名字

方法使用流程

  1. 实例化进程池 p=Pool()。
  2. 通过apply_async()函数向进程池中添加进程。
  3. 通过p.close()函数关闭进程池,关闭之后无法继续添加新的进程。与此同时,CPU开始从进程池中取进程,若CPU有N核,则CPU每次只会取N个进程,待N个进程全部执行完毕,再取出N个。直到将进程池中的进程取完为止。
  4. 通过p.join()函数等待所有进程被取出并执行完毕。
# 如果有大量进程,则可以用进程池,批量添加
from datetime import datetime
from multiprocessing import Process,Pool
import os,time

def music(func,loop):

    for i in range(loop):
        print ("I was listening to %s. %s" %(func,time.ctime()))
        time.sleep(1)

def movie(func,loop):
    for i in range(loop):
        print ("I was at the %s! %s" %(func,time.ctime()))
        time.sleep(5)

if __name__ =='__main__': #执行主进程
    # 主进程
    print('这是主进程,进程编号:%d' % os.getpid())
    t_start = datetime.now()
    pool = Pool()
    for i in range(8): # CPU有几核,每次就取出几个进程
        pool.apply_async(music, args=('爱情买卖',2))
        pool.apply_async(music, args=('阿凡达',3))
    pool.close() # 调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了
    pool.join() # 对Pool对象调用join()方法会等待所有子进程执行完毕
    t_end = datetime.now()
    print('主进程用时:%d毫秒' % (t_end - t_start).microseconds)

multiprocessing 支持进程之间的两种通信通道:Queue(队列)、Pipe(管道)

进程间通信---Pipe(管道)

Pipe可以是单项(half-duplex),也可以是双向(duplex)。通过multiprocessing.Pipe(duplex=False)创建单向管道(默认双向)。一个进程从Pipe一端输入对象,然后被Pipe另一端的进程接收,单向管道只允许管道的一端的进程输入,而双向管道从两端输入。

import multiprocessing

def proc1(pipe):
    pipe.send('hello')
    print('procl rec:',pipe.recv())

def proc2(pipe):
    print('proc2 rec:', pipe.recv())
    pipe.send('hello too')

if __name__=='__main__':
    multiprocessing.freeze_support()
    pipe = multiprocessing.Pipe()

    p1 = multiprocessing.Process(target=proc1,args=(pipe[0],))
    p2 = multiprocessing.Process(target=proc2,args=(pipe[1],))

    p1.start()
    p2.start()
    p1.join()
    p2.join()

执行结果

进程间通信---Queue(队列)

Queue类与Pipe相似,都是先进先出的结构,但是 Queue类允许多个进程放入,多个进程从队列取出对象。
Queue类使用Queue(maxsize)创建,maxsize表示队列中可以存放对象的最大数量。

from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    print('Process to write: %s' % os.getpid())
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('Get %s from queue.' % value)

if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()

执行结果

工具选择

不同的方式适用于不同的场景,可以根据需要选择最合适的方式。

场景应用

  • 多线程、协程---IO密集型(网络IO、磁盘IO、数据库IO)
    • 网路编程(requests请求、文件读取、修改等操作、数据库操作、实时通信、网络游戏等)
    • 爬虫技术
  • 多进程---(利用多核CPU进行并行计算,提高程序的执行效率。)
    CPU密集型计算
    分布式应用
    多设备并行处理相同数据(appium多设备并行运行)---利用了多进程的互不干扰特性,就算某个进程僵死,也不会影响其他进程
    pytest-xdist
    视频的压缩解码任务

多线程编程可以提高程序的效率和执行速度,但需要注意线程安全和*锁**的使用。

标签:__,进阶,Python,编程,worker,started,print,线程,进程
From: https://www.cnblogs.com/upstudy/p/18004192

相关文章

  • JAVA8 - 异步编程
    目录FutureFutureFuture接口在JAVA5中被引入,设计初衷式对将来某个时刻会发生的结果进行建模。它建模了一中异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不再......
  • 【学习笔记】Python 环境隔离
    目录前言venvvenv环境管理venv包管理virtualenv以及virtualenvwrapper安装virtualenvwrapper环境管理virtualenvwrapper包管理condaconda环境管理conda包管理总结参考资料Python作为最常用的脚本语言,有着非常丰富的第三方库,但是这也导致了Python的环境管理非常必要。......
  • 第五章:面向对象编程(基础)
    面向对象概述软件开发方法:面向过程和面向对象面向过程:关注点在实现功能的步骤上PO:ProcedureOriented。代表语言:C语言面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。例如开汽车:启动、踩离合、挂挡、松离合......
  • 跟着王洋老师学编程 - 1.8 打字母游戏
    案例简述在一个300*400的窗体上,有10个随机产生的字母向下落,在键盘上敲字母,如果对了就消掉,初始成绩为1000分,每敲对一个字母加10分,如果字母落到屏幕下方,或者敲错扣100分。我的思路-创建一个窗体-创建一个字母画布类——继承画布类Panel、编写构造方法以初始化数据,实现多线程......
  • Python数据结构与算法03-单循环链表
    单循环链表classListNode:def__init__(self,val,next=None):self.val=valself.next=nextclassSingleLoopLinkList:def__init__(self,node=None):self.__head=nodeifnode:#如果不是空链表node.next=node......
  • 数组的中心位置-od-python
    数组的中心位置时间限制:1s空间限制:256MB限定语言:不限题目描述:给你一个整数数组nums,请计算数组的中心位置。数组中心位置是数组的一个下标,其左侧所有元素相乘的积等于右侧所有元素相乘的积。数组第一个元素的左侧积为1,最后一个元素的右侧积为1如果数组有多个中心位置,应该返回......
  • python信用卡欺诈检测
    信用卡欺诈检测任务流程:1、加载数据,观察问题2、针对问题给出解决方案3、数据集切分4、评估方法对比5、逻辑回归模型6、建模结果分析7、方案效果对比读取数据importpandasaspdimportmatplotlib.pyplotaspltimportnumpyasnp%matplotlibinlinedata=pd.r......
  • Python 机器学习 K-近邻算法 KD树
    在使用K-近邻(KNN)算法时,kd树(k-dimensionaltree)是一种用于减少计算距离次数从而提高搜索效率的数据结构。kd树是一种特殊的二叉树,用于存储k维空间中的数据点,使得搜索最近邻点更加高效。KD树的构造过程是将数据分割成更小的区域,直到每个区域满足特定的终止条件。1、构建KD树在k......
  • tensorflow1.15与numpy、keras以及Python兼容版本对照
    https://blog.csdn.net/m0_74181960/article/details/134253013 报错信息:numpy库版本不兼容问题NotImplementedError:CannotconvertasymbolicTensor(bi_lstm/lstm_encoder_a/fw/fw/strided_slice:0)toanumpyarray.根据错误信息中提到的内容,可能是在创建初始状态时使......
  • C++编程练习||创建一个名为Rational的类,进行分数运算。
    题目:创建一个名为Rational的类,进行分数运算。创建一个名为Rational的类,进行分数运算。用整数变量表示类的private数据-numerator(分子)和denominator(分母)。提供一个带默认值的构造函数,并且它应该以简化的形式保存分数。例如分数2/4应在对象中保存为numerator为1,denominator为2的形式。......