线程、进程与协程
python程序的运行逻辑是,在.py文件从上往下,从左往右加载代码,有python解释器翻译成CPU可读程序后,交给操作系统OS,由操作系统调度CPU执行。
那什么是线程?
线程是操作系统能够进行运算调度的最小单位。它包含在进程中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程执行不同任务
那什么是进程?
执行的一个程序就是一个进程。进程是对线程的资源整合,线程间资源是共有的,但进程间资源是对立的。
什么是协程?
协程,又称微线程,纤程。协程是用户态的轻量级线程
协程本质上就是一个单线程
协程拥有自己的寄存器和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来时,恢复先前保存的寄存器上下文和栈。即协程能保留上一次调用时的状态,过程重入时,进入上次离开时的逻辑流位置
协程优点:
无多线程间切换的开销
无需原子操作锁定及同步开销(各种线程锁)
方便切换控制流,简化编程模型
高并发+高扩展+低成本:一个CPU支持上万协程没有问题。所以适用高并发。
协程缺点:
无法利用CPU的多和资源。协程本身还是一个单线程。无法利用上CPU多核,但可以与进程配合运行
进行阻塞操作时,会阻塞整个程序
进程与线程的区别:
1、线程间资源是共享的,进程存在自己独立的资源
2、多线程数据共享
3、线程间可以通讯,进程不行
4、多进程的创建是完全复制的,消耗较大
5、改变主线程可以影响子线程,主进程不可以
进程与线程不存在快慢,进程与线程执行速度是一致的。
说到解释器,通用的Cpython解释器中存在GIL锁(Global Interpreter lock):在同一时刻,只能有一个线程进入解释器;
造成的影响是:无法调用多核cpu,造成只能调用一个cpu的现象。
故!理论上,python并没有绝对意义上的多线程。
后期使用多进程的方式,使任务并发处理,但此方式并不全面,因为,线程间是可以相互通讯的,资源是共享的,但是进程是不可以的。
- 线程使用:
import threading
#!-*- coding:utf-8 -*-
#date: 2020/4/21
import threading
import time
def foo(n):
print(threading.active_count())
# [<_MainThread(MainThread, started 26644)>, <Thread(Thread-1, started 12656)>]
# [<_MainThread(MainThread, started 26644)>, <Thread(Thread-1, started 12656)>, <Thread(Thread-2, started 26116)>]
print(n)
time.sleep(2)
t1 = threading.Thread(target=foo,args=('第一个线程',)) # 创建进程1
t2 = threading.Thread(target=foo,args=('第一个线程',)) # 创建进程2
t = []
t.append(t1)
t.append(t2)
if __name__ == '__main__':
print(threading.active_count()) # [<_MainThread(MainThread, started 26644)>]
for i in t:
i.start() # 运行进程
i.join() # 阻塞 当进程2结束才继续执行主进程
print('线程结束')
第二种创建方式,类方式创建:
#!-*- coding:utf-8 -*-
#date: 2020/4/21
import threading
import time
class MyThread(threading.Thread):
def __init__(self):
super(MyThread, self).__init__()
def run(self):
print('hello',self.name)
time.sleep(2)
if __name__ == '__main__':
pro_list = []
for i in range(2):
i = MyThread()
pro_list.append(i)
for p in pro_list:
p.start()
p.join()
print('线程结束')
# 运行结果:
'''
hello Thread-1
hello Thread-2
线程结束
'''
结论:
在python中:
如果任务是IO密集型的,可以使用多线程;
如果是计算密集型的,改用C处理。
线程同步锁:
x = threading.Lock()
用
x.acquire()
x.release()
包住
上面在重复使用的过程中,如果锁建立过多,容易造成死锁现象,故引出递归锁(本质是,使用了一个计数器,每增加一次,加1,释放一次,减1):
lock = threading.RLock()
使用方式一致:
lock.acquire()
lock.release()
包住。
- 进程使用:
from multiprocessing import Process
进程的使用与线程时完全一致的。
正常创建:
#!-*- coding:utf-8 -*-
#date: 2020/4/21
from multiprocessing import Process
import time
def foo(n):
print(n)
time.sleep(2)
t1 = Process(target=foo,args=('第一个进程',))
t2 = Process(target=foo,args=('第一个进程',))
t = []
t.append(t1)
t.append(t2)
if __name__ == '__main__':
for i in t:
i.start()
i.join()
print('进程结束')
类式创建:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self):
super(MyProcess, self).__init__()
def run(self):
print('hello',self.name)
time.sleep(2)
if __name__ == '__main__':
pro_list = []
for i in range(2):
i = MyProcess()
pro_list.append(i)
for p in pro_list:
p.start()
p.join()
print('进程结束')
# 运行结果:
'''
hello Process-1
hello Process-2
进程结束
'''
进程之间是无法进行数据交互共享的,注意:可以通过queue
队列或pipes
管道的方式进行数据共享。
简单举例,仅供参考:
from multiprocessing import Process,Queue
import time
class MyProcess(Process):
def __init__(self,q):
self.queue = q
super(MyProcess, self).__init__()
def run(self):
print('hello',self.name)
self.queue.put(self.name)
time.sleep(2)
if __name__ == '__main__':
q = Queue()
pro_list = []
for i in range(2):
i = MyProcess(q)
pro_list.append(i)
for p in pro_list:
p.start()
print('队列获取数据',q.get())
print('队列获取数据',q.get())
print('进程结束')
# 运行结果
'''
hello MyProcess-1
队列获取数据 MyProcess-1
hello MyProcess-2
队列获取数据 MyProcess-2
进程结束
'''