进程、线程、协程的区别


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()