python 线程 进程 协程_python——多线程,多进程,协程

本文详细解析了进程、线程和协程的概念及其在操作系统中的应用,对比了它们之间的区别,并介绍了Python中如何实现多进程、多线程及协程,包括线程锁、信号量、事件等高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程,进程

定义:

进程: 是对各种资源管理的集合,qq 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等

线程: 是操作系统最小的调度单位, 是一串指令的集合。

进程要想操作CPU,就必须要创建一个线程(进程中至少包含一个线程)

区别:

1.线程共享内存空间(共享数据等),进程的内存空间是独立的

2.同一进程的线程之间可以相互交流 ,2个进程之间的交流必须通过一个中间代理

3.线程可以操作和控制其他线程(同一进程下),进程只能操作和控制子进程。

对主线程的更改可能会影响到其他线程的工作,对父进程的更改(除非关闭)不会影响子进程。(子进程还可以派生子进程)

Python中的多线程

import threading

def run(n):

print('运行线程',n)

for i in range(10): # 创建10个线程

t = threading.Thread(target=run, args=(i,)) # 线程运行的函数和参数

t.setDaemon(True) # 设置为守护线程(在主线程线程结束后自动退出,默认为False即主线程线程结束后子线程仍在执行)

t.start() # 启动线程

上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

更多方法:

start            线程准备就绪,等待CPU调度

setName      为线程设置名称

getName      获取线程名称

setDaemon   设置为后台线程或前台线程(默认)

如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止

如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义

run              线程被cpu调度后自动执行线程对象的run方法

互斥锁(Lock、RLock)

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

import threading

import time

gl_num = 0

lock = threading.RLock() # 定义线程锁

def Func():

lock.acquire() # 开始锁

global gl_num

gl_num += 1

time.sleep(1)

print(gl_num)

lock.release() # 结束锁

for i in range(10):

t = threading.Thread(target=Func)

t.start()

"""

没有锁的时候打印:

>> 10,10,10,10,10,10,10,10,10,10

有锁的时候打印:

>> 1,2,3,4,5,6,7,8,9,10

"""

信号量(Semaphore)

互斥锁同时只允许一个线程更改数据,而信号量锁是同时允许一定数量的线程更改数据 ,多个线程同时执行完毕。

import threading, time

def run(n):

semaphore.acquire() # 信号量锁开始

time.sleep(1)

print("当前运行线程为: %s" % n)

semaphore.release() # 结束

if __name__ == '__main__':

num = 0

semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行(5个5个一起出来)

for i in range(20):

t = threading.Thread(target=run, args=(i,))

t.start()

事件(event)

python线程的事件用于主线程控制其他线程的执行,事件主要提供了两个方法:event.set()设定,event.clear()没设定。

event.wait():等待设定

event.is_set():判断是否设定

1 importtime,threading2

3 event =threading.Event()4

5 deflighter():6 count=07 event.set() #设定

8 whileTrue:9 if count<10 and count>=5:10 event.clear() #清除设定

11 print("\033[41;1m红灯\033[0m")12 time.sleep(1)13 elif count>10:14 count=015 event.set()16 else:17 print("\033[42;1m绿灯\033[0m")18 time.sleep(1)19 count+=1

20

21 defcar(name):22 whileTrue:23 if event.is_set(): #判断是否设定

24 print("\033[32;1m[%s] run...\033[0m"%name)25 time.sleep(1)26 else:27 print('\033[31;1m [%s] stop...'%name)28 event.wait() #等待设定

29 print('\033[33;1m [%s]走咯'%name)30

31

32 light=threading.Thread(target=lighter,)33

34 light.start()35

36 car1=threading.Thread(target=car,args=('Tesla',))37

38 car1.start()

红绿灯

Python中的多进程

多进程特点:

每一个进程都是由父进程启动的

子进程被父进程启动后就是独立的(父进程copy了一份给子进程)

from multiprocessing import Process

def foo(i):

print('say hi', i)

if __name__ == '__main__':

for i in range(10):

p = Process(target=foo, args=(i,))

p.start()

获取进程id:

1 from multiprocessing import Process #多进程

2 importos3

4 definfo(title):5 print(title)6 print('module name:', __name__)7 print('parent process:', os.getppid()) #获取父进程的端口号

8 print('process id:', os.getpid()) #获取当前进程的端口号

9 print('\n')10

11 deff(name):12 info('\033[31;1mcalled from child process function f\033[0m')13 print('hello', name)14

15 if __name__ == '__main__':16 info('\033[32;1mmain process line\033[0m')17 p = Process(target=f, args=('bob',))18 p.start()19 p.join()20

21 get进程id

View Code

进程数据共享

进程各自持有一份数据,默认无法共享数据

当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

通过队列共享数据:

from multiprocessing import Process, Queue

def run(qq):

qq.put("123")

if __name__=='__main__':

q=Queue() #生成一个队列,通过队列进行传递数据

p=Process(target=run,args=(q,))

p.start()

print(q.get())

p.join()

通过字典共享数据:

from multiprocessing import Process, Manager

import os

def f(d, l):

d[os.getpid()] =os.getpid()

l.append(os.getpid())

print(l)

if __name__ == '__main__':

with Manager() as manager: #Manager()=manager

d = manager.dict() #{} #生成一个字典,可在多个进程间共享和传递

l = manager.list(range(5))#生成一个列表,可在多个进程间共享和传递

p_list = []

for i in range(10):

p = Process(target=f, args=(d, l))

p.start()

p_list.append(p)

for res in p_list: #等待结果

res.join()

print(d)

print(l)

进程锁:

在多个进程共享一个屏幕时可能会导致输出的数据变乱。

from multiprocessing import Process, Lock

def f(l, i):

l.acquire() #设置进程锁

try:

print('hello world', i)

finally:

l.release() #取消进程锁

if __name__ == '__main__':

l = Lock() #实例化锁

for num in range(10):

Process(target=f, args=(l, num)).start()

进程池:

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

apply:串行

apply_async:并行

1 from multiprocessing importPool2 importtime3 importos4

5 defFoo(i):6 time.sleep(2)7 print("in process",os.getpid())8 return i + 100

9

10 defBar(arg):11 print('-->exec done:', arg,os.getpid())12

13 if __name__ == '__main__':14 #freeze_support()

15 pool = Pool(processes=3) #允许进程池同时放入5个进程

16 print("主进程",os.getpid())17 for i in range(10):18 pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回调

19 #pool.apply(func=Foo, args=(i,)) #串行

20 #pool.apply_async(func=Foo, args=(i,)) #并行

21 print('end')22 pool.close()23 pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。.join()

View Code

协程(微线程)

通过单线程实现并发(协程只有一个线程,so不用锁)

协程的好处:

无需线程上下文切换的开销

无需原子操作锁定及同步的开销

"原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。

方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

使用yield实现协程操作例子

1 importtime2

3 defconsumer(name):4 print("--->starting eating baozi...")5 whileTrue:6 new_baozi = yield

7 print("[%s] is eating baozi %s" %(name, new_baozi))8 #time.sleep(1)

9

10 defproducer():11 r = con.__next__()12 r = con2.__next__()13 n =014 while n < 5:15 n += 1

16 con.send(n)17 con2.send(n)18 time.sleep(1)19 print("\033[32;1m[producer]\033[0m is making baozi %s" %n)20

21 if __name__ == '__main__':22 con = consumer("c1")23 con2 = consumer("c2")24 p =producer()25

26 yield

View Code

同步与异步性能差别

1 importgevent2

3 deftask(pid):4 gevent.sleep(0.5)5 print('Task %s done' %pid)6

7 def synchronous(): #每个都要等0.5s,需要5s

8 for i in range(1, 10):9 task(i)10

11 def asynchronous(): #一共等0.5s

12 threads = [gevent.spawn(task, i) for i in range(10)]13 gevent.joinall(threads)14

15 print('Synchronous:')16 synchronous()17

18 print('Asynchronous:')19 asynchronous()20

21 同步与异步

View Code

总结:

1、多进程,多线程,协程

操作系统方面的多进程,多线程,协程:

操作系统可以开多个进程,一个进程可以有多个线程,多个线程可以被分配到不同的核心上跑,但实际上每个核心上只有一个线程,只是这个线程在不停的进行上下文的切换,给我们一种并发的感觉。

协程:单线程的调度机制。它的作用是让原来要使用异步+回调方式(调用线程)写的非人类代码,可以用看似同步的方式写出来。协程是先出现的,但它有明显的时间差,没有并发的感觉,所以出现了线程。

python的多进程,多线程,协程:

但python的多线程只能在一个核心上跑(创始人没想到会有多核出现),就是单核的上下文切换,所以很鸡肋。于是协程在python大展拳脚,好多框架都是使用协程来解决多任务的,而不是线程(scrapy,tornado)。

python中多进程,多线程,协程的使用:

IO密集型:多线程/协程(可以用异步),cpu占用率低,单个cpu核心就够了

CPU密集型:多进程,多给它几个核心提升性能

2、python多线程不用join,进程需要(否则子进程会在进程结束时强制被关闭)

1 python 默认参数创建线程后,不管主线程是否执行完毕,都会等待子线程执行完毕才一起退出,有无join结果一样

2 如果创建线程,并且设置了daemon为true,即thread.setDaemon(True), 则主线程执行完毕后自动退出,不会等待子线程的执行结果。而且随着主线程退出,子线程也消亡。

3 join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。

4 如果线程daemon属性为False, 则join里的timeout参数无效。主线程会一直等待子线程结束。

5 如果线程daemon属性为True, 则join里的timeout参数是有效的, 主线程会等待timeout时间后,结束子线程。此处有一个坑,即如果同时有N个子线程join(timeout),那么实际上主线程会等待的超时时间最长为 N * timeout, 因为每个子线程的超时开始时刻是上一个子线程超时结束的时刻。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值