多线程、多进程、协程学习笔记

多线程、多进程、协程

  • 进程(’资源单位’):运行中的程序,每次我们执行一个程序,咱们的操作系统对自动的为这个程序准备一些必要的资源(例如,分配内存,创建一个能够执行的线程)
  • 线程(’执行单位’):程序内,可以直接被CPU调度的执行过程,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

多线程

写法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from threading import Thread

def func(name):
for i in range(100):
print(name,i)

if __name__=='__main__':
t1=Thread(target=func,args=('love',))
t2=Thread(target=func,args=('you',))
t3=Thread(target=func,args=('i',))

t2.start()
t1.start()
t3.start()

写法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from threading import Thread

class MyThread(Thread):
def _init_(self,name):
super(MyThread,self)._init_()
self.name = namel

def run(self):
for i in range(100):
print(self.name,i)

if __name__='__main__':
t1=MyThread('I')
t2=MyThread('love')
t3=MyThread('you')

t1.start()
t2.start()
t3.start()

线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from concurrent.futures import ThreadPoolExecutor

def func(name,t):
time.sleep(t)
for i in range(10):
print(name)
return name

def fn(res):
`打印返回的结果`
print(res.result())

if __name__=='__main__':
with ThreadPoolExecutor(3) as t:
t.submit(func,'周杰伦',2).add_done_callback(fn)
t.submit(func,'薛之谦',1).add_done_callback(fn)
t.submit(func,'余华',3).add_done_callback(fn)

#t.submit().add_done_callback()返回即执行
#返回callback执行的顺序是不确定的

result=t.map(func,['周杰伦','薛之谦','余华'],[2,1,3])
for r in rusult:
print(r)
#map返回值是生成器,返回的内容和任务分发的顺序是一致的

多进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from multiprocessing import Process

def func(name,t):
time.sleep(t)
for i in range(10):
print(name)
return name

if __name__=='__main__':
p1=Process(target=func,args=('i',))
p2=Process(target=func,args=('love',))
p3=Process(target=func,args=('you',))

p1.start()
p2.start()
p3.start()

进程间传递数据(中间件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#此处将Queue作为中间件
from multiprocessing import Process,Queue
from concurrent.futures import ThreadPoolExecutor

def get_img_src(q):
#获取到src后通过put向队列里装数据
q.put(src)

def download(url):
print('开始下载')
with open('./img.'+name,mode='wb') as f:
resp=requests.get(url)
f.write(resp.content)
print('下载完毕',url)

def download_img(q):
with ThreadPoolExecutor(10) as t:
while 1:
src=q.get() #从队列中获取数据,如果没数据就会阻塞
t.submit(download,src)
# 此处判断是否应该结束,结束就break终止循环

if __name__=='__main__':
q=Queue()
p1=Process(target=get_img_src,args=(q,))
p2=Process(target=download_img,args=(q,))
p1.start()
p2.start()

**补充:在网络编程中,队列和 socket 经常结合使用,以下是一些应用场景

  • 请求处理:服务器端可能会使用一个队列来管理来自客户端的 socket 请求。当请求到达时,它们被放入队列中,然后服务器按顺序处理这些请求。
  • 异步处理:在处理非阻塞 I/O 时,可以使用队列来管理待处理的 socket 事件。例如,一个线程可以监听 socket 连接,并将新的连接请求放入队列中,然后由其他工作线程来处理这些连接。
  • 消息队列:在网络应用程序中,可以使用消息队列(如 RabbitMQ、Kafka)来缓冲和转发 socket 消息,确保消息的可靠传递和处理。**

使用场景:

  • 多线程:任务相对统一,相互类似
  • 多进程:多个任务相互独立,很少有交集
    • 例:免费IP池
    1. 从免费网站抓取代理IP
    2. 验证代理IP是否可用
    3. 准备对外的接口
  • 协程:能够更加高效的利用CPU

多任务异步协程

协程(Coroutine)是一种程序组件,它允许多个入口点用于暂停和恢复执行的函数,可以在单个线程内实现多任务的并发执行。协程提供了一种更加轻量级的并发单元,相比于线程和进程,协程间的切换开销更小,因为它们共享同一线程的堆栈空间。

  • 协作式多任务:协程通过显式的暂停(yield)和恢复(resume)操作来切换执行,这种切换是协作式的,而非抢占式的。
  • 轻量级:协程不像线程那样涉及操作系统的上下文切换,它们通常由程序自身控制,因此创建和切换开销较小。
  • 非阻塞I/O操作:协程常用于执行非阻塞I/O操作,可以在等待I/O时让出CPU给其他协程执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#执行协程对象的方法

import asyncio

async def func():
#该函数执行时获得的是一个协程对象
pass

if __name__=='__main__':
#协程对象想要执行,必须借助event_loop
func()
#获取事件循环
event_loop=asyncio.get_event_loop()
#event_loop执行协程对象,直到该对象内的内容执行完毕为止
event_loop.run_until_complete(func())

#该语句可能会报错Event Loop has closed!!!
asyncio.run(func())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#多任务执行协程对象

import asyncio
import time

async def func1():
print('func1开始')
await asyncio.sleep(1)
print('func1结束')

async def func2():
print('func2开始')
await asyncio.sleep(2)
print('func2结束')

async def func3():
print('func3开始')
await asyncio.sleep(3)
print('func3结束')

if __name__=='__main__':
f1=func1()
f2=func2()
f3=func3()
tasks=[
f1,
f2,
f3,
]
asyncio.run(asyncio.wait(tasks))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#返回值的获取

import asyncio

async def download(url):
pass

async def main():
#假设已经获取到下载链接
urls=[
'xxx.com',
'xxx.com',
'xxx.com'
]
#封装任务列表
tasks=[]
for url in urls:
#创建任务
task=asyncio.create_task(download(url))
#将任务扔到列表,为了统一处理
tasks.append(task)
#asyncio.wait()返回的结果是无序的
done,pending=await asyncio.wait(tasks)
for t in done:
#t是任务,result()是任务的返回值
print(t.result())
#asyncio.gather()返回值是有序的(按照添加任务顺序返回)
#return_exceptions=True,如果有错误信息,返回错误信息,其他任务正常执行
result=await asyncio.gather(*tasks,return_exceptions=True)

if __name__=='__main__':
#方法一:
asyncio.run(main())

#方法二:
event_loop=asyncio.get_event_loop()
event_loop.run_until_complete(main())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import asyncio
import aiohttp
import aiofiles

async def download(url):
#相当于requests
async with aiohttp.ClientSession() as session:
#发送网络请求
async with seesion.get(url) as resp:
text=await resp.text() #->resp.text
content=await resp.content.read() #->resp.content
#写入文件
async with aiofiles.open(file_name,mode='wb') as f:
await f.write(content)
async def main():
#同上
pass


if __name__=='__main__':
asyncio.run(main())

多线程、多进程、协程学习笔记
https://maplelea1f.github.io/2024/09/02/多线程、多进程、协程学习笔记/
作者
Maple
发布于
2024年9月2日
许可协议