python 多进程 多线程 协程_简单讲讲多进程、多线程以及协程

本文介绍了Python中的多进程、多线程和协程的基础概念及其区别。进程是操作系统分配资源的基本单位,线程是CPU调度的最小单位。Python实现多进程可通过`multiprocessing`库,多线程则使用`threading`库。GIL锁使得Python的多线程在CPU密集型任务中表现不佳,但在IO密集型任务中有优势。协程能有效减少线程切换的开销,提高运行效率,`asyncio`库提供了方便的协程支持。
摘要由CSDN通过智能技术生成

一、基础概念描述

1.什么是进程

一个程序的执行实例就是一个进程,是一个动态概念,是操作系统进行资源(CPU、内存、磁盘、IO、带宽等)分配的基本(最小)单位,所以每个进程包含了程序执行过程中的所有资源。 进程间的数据交换需要中间件来进行传递。

一个python脚本(排除整个脚本用多进程方式编写)运行就是一个进程

# 以最简单的为例

print ('hello word')

1.2 什么是线程

线程是cpu的最小调度单位,同属一个进程里面的线程共享所有资源。

一个进程可由多个线程的执行单元组成,每个线程都运行在同一进程的上下文中,共享同样的代码和全局数据,所以线程间的数据交换会来得容易些

当我们直接运行脚本时,操作系统就会先创建一个进程,这个进程就会创建一个主线程,然后主线程会再创建其他的子线程(若有)。

二、 进程与线程的区别

1.进程是操作系统分配的最小单位,线程是CPU调度的最小单位

2.一个进程由一个或者多个线程组成,线程是一个进程中代码的不同执行路线

3.切换进程需要的花销(时间、资源等)比切换线程要大,切换线程也是需要花销的

4.进程间不能直接通信

通俗理解线程和进程的关系:

进程是火车头,线程是车厢

1.一个进程可以有多个线程-->一个火车头可以有多个车厢

2.进程间不能直接通信-->一个火车头不能直接走到另外一个火车头

3.线程间共享所有资源,可以直接通信-->一个人可以从一个车厢走到另外一个车厢

4.进程要比线程消耗更多的计算机资源。->多列火车总比多个车厢要贵

三、python实现多进程

3.1 多进程方法一:将方法作为进程

# import 多进程库

import multiprocessing

def worker1(name):

print("worker1 name is " + name)

def worker2(name):

print('worker2 name is' + name )

if __name__ == "__main__":

# target后面传入要多进程的方法,args以元组的方式传入参数

# 创建两个进程分别调用不同的方法

p1 = multiprocessing.Process(target=worker1, args=('subprocess1',))

p2 = multiprocessing.Process(target=worker2, args=('subprocess2'))

#启动进程

p1.start()

p2.start()

#停止进程

p1.join()

p2.join()

3.2 多进程方法二:将类作为进程

import multiprocessing

import time

# 若在调试模式下运营,要将下面的注释恢复,不然会报错

# multiprocessing.set_start_method('spawn',True)

# 通过继承的方式来实现

class MyProcess(multiprocessing.Process):

def __init__(self, loop):

multiprocessing.Process.__init__(self)

self.loop = loop

# 需要重写run方法,把业务逻辑塞到这个方法下

def run(self):

for count in range(self.loop):

time.sleep(1)

print('Pid: ' + str(self.pid) + ' LoopCount: ' + str(count))

if __name__ == '__main__':

for i in range(2, 5):

p = MyProcess(i)

p.start()

进程间的通信可以通过队列(Queue)来进行实现,Queue是多进程安全的队列,主要有两个方法put和get。关于Queue的更多用法可以自行百度下

3.3 通过队列Queue实现生产者-消费者模式

import multiprocessing

multiprocessing.set_start_method('spawn',True)

def Producer(q):

try:

q.put(1, block = False)

except:

pass

def Consumer(q):

try:

print (q.get(block = False))

except:

pass

if __name__ == "__main__":

# 创建一个队列

q = multiprocessing.Queue()

# 把队列作为参数传入

producer = multiprocessing.Process(target=Producer, args=(q,))

producer.start()

# 把队列作为参数传入

consumer = multiprocessing.Process(target=Consumer, args=(q,))

consumer.start()

consumer.join()

producer.join()

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

3.3 通过队列Pipe实现生产者-消费者模式

队列用方法作为进程,Pipe就用类作为进程来试试吧

import multiprocessing

class Consumer(multiprocessing.Process):

def __init__(self, pipe):

multiprocessing.Process.__init__(self)

self.pipe = pipe

def run(self):

# 消费者发送信息'Consumer Words'

self.pipe.send('Consumer Words')

# 消费者接收来自管道的信息

print ('Consumer Received:', self.pipe.recv())

class Producer(multiprocessing.Process):

def __init__(self, pipe):

multiprocessing.Process.__init__(self)

self.pipe = pipe

def run(self):

# 生产者接收来自管道另一端的信息'Consumer Words'

print ('Producer Received:', self.pipe.recv())

# 生产者发送信息'Producer Words'

self.pipe.send('Producer Words')

if __name__ == '__main__':

pipe = multiprocessing.Pipe()

p = Producer(pipe[0])

c = Consumer(pipe[1])

p.start()

c.start()

p.join()

c.join()

大家运行下就知道什么效果了。关于多进程还有很多一些其他东西,例如:锁、事件、信号量、进程池、守护进程等,请大家自行百度,这里就不再细讲

四、python实现多线程

4.1 前言-GIL锁

在讲python的多线程时,不得不先讲一下python大名鼎鼎的GIL锁-Global Interpreter Lock(全局解释器锁)。

简单的说,GIL规定了线程在运行时,需要先拿到通行证,否则就不能运行,也就意味着一个python的进程里,无论你有多少个线程,永远只能单线程运行。

插入一个小知识点,还记得上文说过,线程是cpu最小调度单位吗?也就意味着,python多线程是无法使用多核的,但是多进程是可以利用多核的

4.2 那是不是python的多线程就是没用呢?

直接说结论:如果你的程序是CPU密集型的,那么python的多线程是完全没有意义,甚至由于线程切换的花销,会导致更慢点。

但如果你的是IO密集型,那么多线程的提升还是很明显的。

再插入一个小知识点: IO可以简单理解成对数据的读写。例如:等待网络数据到来、从文件中读/写数据等。

P.S. 一般web应用也是IO密集型,所以做了个服务端,响应很慢的话要看看是不是自己代码问题,而不是怪GIL

4.3 多线程方法一:将方法作为线程

# import 线程库

import threading

# 这个函数名可随便定义

def run(n):

print("current task:", n)

if __name__ == "__main__":

# 创建线程

t1 = threading.Thread(target=run, args=("thread 1",))

t2 = threading.Thread(target=run, args=("thread 2",))

t1.start()

t2.start()

4.4 多线程方法二:将类作为线程

import threading

# 继承线程库的类

class MyThread(threading.Thread):

def __init__(self, n):

super(MyThread, self).__init__()

self.n = n

# 重构run方法,把业务逻辑写下面

def run(self):

print("current task:", n)

if __name__ == "__main__":

t1 = MyThread("thread 1")

t2 = MyThread("thread 2")

t1.start()

t2.start()

五、关于进程以及线程的进一步讲解

5.1 什么是协程

协程是属于一种操作,是由用户自己去操作线程的切换(在用户态进行切换),这样的话,就可以大大降低线程切换(在内核态切换)的花销。

补充个小知识点,线程的切换一般是由cpu控制。假设为一个单核的cpu,那么同一时间只会有一个线程运行。这时候如果有多个线程任务运行,那么cpu将会根据当前线程的状态进行调度。

协程就是当代码中出现有io处理的时候,先代码自行调度,将这个操作挂起,然后去继续执行其他操作。这样的话,cpu就不会因为代码中出现io处理进行线程切换,从而减少线程切换的花销,提升运行速度

5.2 协程跟进程、线程的区别

协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。

2.一个进程可以包含多个线程,一个线程可以包含多个协程。

3.一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。

4.协程与进程一样,切换是存在上下文切换问题的。

5.3 实现协程的方法一:使用yield的方法

def Consumer():

r = ''

while True:

n = yield r

if not n:

return

print('Consumer Get %s...' % n)

r = '200 OK'

def Producer(c):

c.send(None)

n = 0

while n < 5:

n = n + 1

print('Producer Words %s...' % n)

r = c.send(n)

print('Consumer return: %s' % r)

c.close()

c = Consumer()

Produce(c)

由于篇幅有限,yield的具体使用方法不在这里详细讲。yield这个关键字本身就是为了上下文切换而生的,换句话说是为了协程而生的

5.4 实现协程的方法一:使用asyncio库

自己通过yield一点点实现肯定非常复杂的,所以可以借助大神的力量

import asyncio

# 需要利用队列来进行协程之间的数据交换

queue = asyncio.Queue()

async def Producer():

n = 0

while True:

await asyncio.sleep(2)

print('add value to queue:',str(n))

await queue.put(n)

n = n + 1

async def Consumer():

while True:

try:

r = await asyncio.wait_for(queue.get(), timeout=1.0)

print('consumer value>>>>>>>>>>>>>>>>>>', r)

except asyncio.TimeoutError:

print('get value timeout')

continue

except:

break

print('quit')

loop = asyncio.get_event_loop()

tasks = [Producer(), Consumer()]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

通过代码我们可以发现,两个方法都有无限循环在里面。若是采用非协程的做法,就需要多进程或者多线程的方式来实现两个无限循环方法间的交互(无限循环会阻塞线程)。但使用协程的时候,我们可以让程序在我们需要的时候让出控制权,从而执行我们需要的代码。通过协程(一个线程)完成了多进程、多线程的消费者模型

最后

在写这篇文章的时候,限于篇幅有很多概念、区别等没有完全讲述清楚,后续会继续深入探讨

Thank you for attention

To Be Continued

喜欢的点个在看呗

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值