Python的并发和网络

并发

在计算机中,如果你的程序在等待,通常是因为以下两个原因:

  • I/O限制
    这个限制很常见。计算机的CPU速度非常快——比计算机内存快几百倍,比硬盘或者网络快几千倍。
  • CPU限制
    在处理数字运算任务时,比如科学计算或者图形计算,很容易遇到这个限制

进程

可以用很多方法来实现任务队列。对单机来说,标准库的multiprocessing模块有一个Queue函数。接下来模拟一个洗盘子的人和多个烘干进程,我们使用一个中间队列dish_queue。

关于multiprocessing的queue与joinablequeue的区别

关于守护进程

守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。

Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。

代码

import multiprocessing as mp
import time

def washer(dishes, output):
    for dish in dishes:
        print('Washing', dish, 'dish')
        time.sleep(5)
        # 把洗好的碟子放在等待烘干队列上
        output.put(dish)

def dryer(input):
    while True:
        # 如果队列中没有任务可以get,该进程应该会陷入阻塞
        dish = input.get()
        print('Drying', dish, 'dish')
        time.sleep(10)
        # 完成get到的任务
        input.task_done()

# 需要if __name__ == '__main__'的原因:https://stackoverflow.com/questions/18204782/runtimeerror-on-windows-trying-python-multiprocessing
if __name__ ==  '__main__':
    dish_queue = mp.JoinableQueue()
    # 其实可以产生多个dryer进程,这里只生成了一个dryer进程
    dryer_proc = mp.Process(target=dryer, args=(dish_queue,))
    # 以守护进程的形式运行该进程
    dryer_proc.daemon = True
    dryer_proc.start()

    dishes = ['salad', 'bread', 'entree', 'dessert']
    washer(dishes, dish_queue)
    # 表示所有碟子都洗完了
    dish_queue.join()

结果:

Washing salad dish
Washing bread dish
Drying salad dish
Washing entree dish
Washing dessert dish
Drying bread dish
Drying entree dish
Drying dessert dish

线程

线程运行在进程内部,可以访问进程的所有内容,有点像多重人格。multiprocessing模块有一个兄弟模块threading,后者用线程来代替进程。multiprocessing和threading的区别之一就是threading没有terminate()函数。很难终止一个正在运行的线程,因为这可能会引起代码和失控连续性上的各种问题。

import threading, queue
import time
def washer(dishes, dish_queue):
    for dish in dishes:
        print('Washing', dish)
        time.sleep(5)
        dish_queue.put(dish)

def dryer(dish_queue):
    while True:
        dish = dish_queue.get()
        print('Drying', dish)
        time.sleep(10)
        dish_queue.task_done()

dish_queue = queue.Queue()
# 其实可以产生多个dryer线程,这里只生成了一个dryer线程
dryer_thread = threading.Thread(target=dryer, args=(dish_queue,))
dryer_thread.start()

dishes = ['salad', 'bread', 'entree', 'desert']
washer(dishes, dish_queue)
dish_queue.join()

结果:

Washing salad
Washing bread
Drying salad
Washing entree
Washing desert
Drying bread
Drying entree
Drying desert
# 开两个线程
for n in range(2):
    dryer_thread = threading.Thread(target=dryer, args=(dish_queue,))
    dryer_thread.start()

结果:

Washing salad
Drying salad
Washing bread
Washing entree
Drying bread
Washing desert
Drying entree
Drying desert

注意更多
在Python中,线程不能加速受CPU限制的任务,原因是标准Python系统中使用了全局解释器锁(GIL)。GIL的作用是避免Python解释器中线程问题,但是实际上会让多线程程序运行速度比对应但线程版本甚至是多进程版本更慢。

对于Python有以下建议:

  1. 使用线程来解决I/O限制问题
  2. 使用进程、网络或者事件来处理CPU限制问题

解决同步IO问题的多进程、多线程以及异步IO

在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。

因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的进程或者线程不受影响。

多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

这里写图片描述

异步IO

消息模型是如何解决同步IO必须等待IO操作这一问题的呢?

异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程

1. 当遇到IO操作时,代码只负责发出IO请求,不等待IO结果,然后直接结束本轮消息处理,进入下一轮消息处理过程

2. 当IO操作完成后,主线程将收到一条“IO完成”的消息,处理该消息时就可以直接获取IO操作结果

在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息

这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。

关于如何提高Web服务端并发效率的异步编程技术

怎样理解阻塞非阻塞与同步异步的区别?

阻塞非阻塞:调用者的角度
同步异步:被调用者的角度

例子:
老张爱喝茶,废话不说,煮开水。

出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

1.老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻

2.老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。

3.老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大

4.老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。所谓同步异步,只是对于水壶而言。

普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下

所谓阻塞非阻塞,仅仅对于老张而言。立等的老张,阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。

虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

事件驱动模型

事件驱动模型就是一种非阻塞IO的具体实现(利用中间层来协调IO操作和CPU的操作)。

事件处理的机制里应该有个事件处理器,事件处理器位于元素和事件处理方法的中间位置,我们在定义事件的时候就是等于在事件处理器里定义元素和事件处理方法的关系

当这种对应关系定义好后,事件处理器就会启动一个死循环,这个循环反复检测元素的状态变化,当事件处理器发现某个状态产生了变化,处理器就会找到对应的事件处理方法,然后执行这个方法。

事件驱动编程实现的核心技术就是能让方法变成对象能在事件处理的流程里传递,方法得到事件管理器的指令后在合适的位置上被促发,这就是回调函数,而JavaScript语言里函数可以当做对象传递,也就保证了事件驱动编程上升到语言层级变成了可能,我想这就是nodejs作者使用google的V8引擎设计出nodejs的重要原因之一。

何时采用事件驱动模型

程序中有许多任务,而且任务之间高度独立(因此它们不需要互相通信,或者等待彼此),而且在等待事件到来时,某些任务会阻塞

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

更多: 基于线程与基于事件的并发编程之争

协程

http://blog.csdn.net/kobejayandy/article/details/11856735

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432090171191d05dae6e129940518d1d6cf6eeaaa969000

http://python.jobbole.com/86481/

http://blog.csdn.net/qq910894904/article/details/41699541

https://www.zhihu.com/question/20511233

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值