进程、线程、协程的区别
GIL(全局解释性锁):
无论启多少个线程,有多少个cpu,Python在执行的时候会在同一时刻只允许一个线程运行。
一个线程需要执行任务,必须获取GIL。
好处:直接杜绝了多个线程访问内存空间的安全问题。
坏处:Python的多线程不是真正多线程,不能充分利用多核CPU的资源。
线程锁(互斥锁):
线程锁保证同一时刻只有一个线程修改内存空间的同一数据,GIL保证同一时刻只有一个线程在运行。
多线程同时修改同一数据,可能会导致数据不准确既线程不安全。
进程:
系统资源分配的最小单位,需要自己独立的内存空间,进程间数据不共享,开销大,效率低。
可以实现并行(进程数小于CPU数的情况下)和并发。
稳定性好,一个子进程崩溃不会影响其他进程。
若进程过多,操作系统调度会出现问题。
适合密集CPU计算业务。
线程:
依赖进程存在,多个线程之间数据共享、全局变量共享,相对进程效率更高。
因为GIL的存在,在同一进程里只能实现并发。
如果需要保证线程安全,需要加锁控制(锁会降低效率)。
相对进程效率高,适合IO密集型的多任务操作。
协程:
又称微线程,拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈几乎没有内核的切换开销。
可以不加锁访问全局变量。
数量上可以是无限个,和多线程比,线程数量越多,协程的性能优势就越明显。
不需要原子操作锁定及同步的开销。
高并发、高扩展性、低成本、效率高。
处理网络I/O性能比较高。
处理CPU计算密集型的时候,性能较低。
本质是单线程,不能同时利用CPU多核,需要和进程配合才能运行在多CPU上,只能实现并发。
打个比方来说明进程、线程、协程的问题:
假设有一个操作系统,是单核的,系统上没有其他的程序(保证单进程)运行,有两个线程 A 和 B ,A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。现在 A B 两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9 秒。如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。如果系统是双核的,那么 A B 两个线程就可以真并行(开启两个进程),总时间只需要 10 秒,而协程的方案仍然需要 20.1 秒。
单线程和多线程的效率问题
在运行程序的过程中,主要是处理网络IO、CPU计算、磁盘IO几种情况。
执行网络IO操作
python解释器在遇到网络IO操作阻塞时会自动释放GIL供其他线程获取。如果是遇到网络IO的操作,因为python解释器会自动释放锁,网络IO过程中本就有些延迟,此时多线程的效率会根据网络延迟情况比单线程高很多。
执行纯计算操作
如果是纯计算的程序,没有网络IO操作,python解释器会每隔100次(次数可以通过sys.setcheckinterval调整)操作释放这把锁。
python在想要执行某个线程的前提是必须拿到GIL这把锁才能进入CPU执行,每次释放GIL,线程进行锁竞争、切换线程,会消耗资源,但python里一个进程永远只能执行一个线程,所以无论CPU的核数是几核,此时python的多线程和单线程相比效率并不高,改成C语言操作此类计算效果会比较好。
使用python创建进程、线程、协程demo
python通过多进程实现多任务demo
方法一:使用multiprocessing模块: 创建Process的实例
import multiprocessing
import time
def task1():
while True:
time.sleep(1)
print("I am task1")
def task2():
while True:
time.sleep(2)
print("I am task2")
if __name__ == '__main__':
p1 = multiprocessing.Process(target=task1) # multiprocessing.Process创建了子进程对象p1
p2 = multiprocessing.Process(target=task2) # multiprocessing.Process创建了子进程对象p2
p1.start() # 子进程p1启动
p2.start() # 子进程p2启动
print("I am main task") # 这是主进程的任务
方法二:使用进程池Pool
import multiprocessing
import time
def task1():
while True:
time.sleep(1)
print("I am task1")
def task2():
while True:
time.sleep(2)
print("I am task2")
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=2) # 创建包含2条进程的进程池,使用池可以有效控制进程池的最大数量
pool.apply_async(task1) # 实现异步
pool.apply_async(task2)
pool.close() # 关闭进程池。在调用该方法之后,该进程池不能再接收新任务,它会把当前进程池中的所有任务执行完成后再关闭自己。
pool.join() # 等待所有进程完成。
python通过多线程实现多任务demo
import threading
from time import sleep
def task1(): # 线程函数1
for i in range(0, 9):
print("I am task1")
def task2(): # 线程函数2
print('I am task2')
sleep(1)
t1 = threading.Thread(target=task1) # 线程一
t2 = threading.Thread(target=task2) # 线程二
t1.start() # 开始线程一
# t1.join() # 线程等待,程序会停留在这里,等线程一执行完之后再继续执行下面的代码。
t2.start() # 开始线程二
python通过协程实现多任务demo
方法一:使用gevent创建协程
import gevent
from gevent import monkey
import time
import random
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作代码,换为 gevent 中自己实现的模块
def task1():
for i in range(10):
print('I am task1', i)
time.sleep(random.random())
def task2():
for i in range(10):
print('I am task2', i)
time.sleep(random.random())
gevent.joinall([ # 将协程任务添加到事件循环,接收一个任务列表
gevent.spawn(task1), # 创建一个普通的执行单元对象并切换
gevent.spawn(task2)
])
# 等价于
'''
g1 = gevent.spawn(work, 'work1')
g2 = gevent.spawn(work, 'work2')
g1.join()
g2.join()
'''
方法二:使用asyncio创建协程
import asyncio
@asyncio.coroutine # 把一个generator(生成器)标记为coroutine类型,然后把这个coroutine扔到EventLoop中执行。
def task1():
for i in range(10):
yield from asyncio.sleep(1) # yield from 调用另外一个generator 然后sleep 1秒,模拟IO延迟
print('I am task1')
@asyncio.coroutine
def task2():
for i in range(10):
yield from asyncio.sleep(1)
print('I am task2')
loop = asyncio.get_event_loop() # 获取EventLoop,相当于一个循环
tasks = [task2(), task1()]
function(){ //外汇返佣 http://www.fx61.com/
loop.run_until_complete(asyncio.wait(tasks)) # 执行EventLoop中的coroutine
loop.close()
python使用多进程和协程结合实现多任务demo
from multiprocessing import Pool
import gevent
def task1():
for i in range(10):
gevent.sleep(2)
print('I am task1')
def task2():
for i in range(10):
gevent.sleep(2)
print('I am task2')
def coroutine():
gevent.joinall([
gevent.spawn(task1),
gevent.spawn(task2)
])
if __name__ == "__main__":
p = Pool() # 不加数字,默认为当前CPU核数
for i in range(3):
p.apply_async(coroutine, args=())
p.close()
p.join()