无论启多少个线程,有多少个 cpu , Python 在执行的时候会在同一时刻只允许一个线程运行。 好处:直接杜绝了多个线程访问内存空间的安全问题。 坏处: Python 的多线程不是真正多线程,不能充分利用多核 CPU 的资源。 线程锁(互斥锁): 线程锁保证同一时刻只有一个线程修改内存空间的同一数据, GIL 保证同一时刻只有一个线程在运行。 多线程同时修改同一数据,可能会导致数据不准确既线程不安全。 进程: 系统资源分配的最小单位,需要自己独立的内存空间,进程间数据不共享,开销大,效率低。 可以实现并行(进程数小于 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 几种情况。 python 解释器在遇到网络 IO 操作阻塞时会自动释放 GIL 供其他线程获取。如果是遇到网络 IO 的操作,因为 python 解释器会自动释放锁,网络 IO 过程中本就有些延迟,此时多线程的效率会根据网络延迟情况比单线程高很多。 执行纯计算操作 如果是纯计算的程序,没有网络 IO 操作, python 解释器会每隔 100 次(次数可以通过 sys.setcheckinterval 调整)操作释放这把锁。 python 在想要执行某个线程的前提是必须拿到 GIL 这把锁才能进入 CPU 执行,每次释放 GIL ,线程进行锁竞争、切换线程,会消耗资源,但 python 里一个进程永远只能执行一个线程,所以无论 CPU 的核数是几核,此时 python 的多线程和单线程相比效率并不高,改成 C 语言操作此类计算效果会比较好。 方法一:使用 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 print("I am main task") # 这是主进程的任务 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() # 关闭进程池。在调用该方法之后,该进程池不能再接收新任务,它会把当前进程池中的所有任务执行完成后再关闭自己。 import threading from time import sleep for i in range(0, 9): print("I am task1") print('I am task2') sleep(1) t1 = threading.Thread(target=task1) # 线程一 t2 = threading.Thread(target=task2) # 线程二 # t1.join() # 线程等待,程序会停留在这里,等线程一执行完之后再继续执行下面的代码。 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() ''' 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() 更多技术资讯可关注:itheimaGZ 获取 |