- 目录
- 回顾同步与异步编程
- 回顾多线程编程
- yield与协程
- 异步编程同步化
同步编程即线性化编程,代码按照既定顺序执行,上一条语句执行完才会执行下一条,否则就一直等在那里。
但是许多实际操作都是CPU 密集型任务和 IO 密集型任务,比如网络请求,此时不能让这些任务阻塞主线程的工作,于是就会采用异步编程。异步的标准元素就是回调函数(Callback, 后来衍生出Promise/Deferred概念),主线程发起一个异步任务,让其自己到一边去工作,当其完成后,会通过执行预先指定的回调函数完成后续任务,然后返回主线程。在异步任务执行过程中,主线程无需等待和阻塞,可以继续处理其他任务。
下例大家并不陌生,是jQuery标准发送http异步请求的方式。
1
2
3
4
5
6
7
$.ajax({
url:"/echo/json/",
success: function(response)
{
console.info(response.name);
}
});
而并发的核心思想在于,大的任务可以分解成一系列的子任务,后者可以被调度成 同时执行或异步执行,而不是一次一个地或者同步地执行。两个子任务之间的 切换也就是上下文切换。
回顾多线程编程当主线程发起异步任务,这个任务跑到哪里去工作了呢?这就说到多线程(包括多进程)编程,一个主线程可以主动创建多个子线程,然后将任务交给子线程,每个子线程拥有自己的堆栈空间。操作系统可以通过分时的方式让同一个CPU轮流调度各个线程,编程人员无需关心操作系统是如何工作的。
但是如果需要在多个线程之间通信,则需要编程人员自己写代码来控制线程之间的协作(利用锁或信号量)以及通信(利用管道、队列等)。
经典的Producer-Consumer问题这个问题说的是有两方进行通信和协作,一方只负责生产内容,另一方只负责消费内容。消费者并不知道,也无需知道生产者何时生产,只是当有内容生产出来负责消费即可,没有内容时就等待。这是一个经典的异步问题。
Threading/Queue方案传统的解决方案即是采用多线程来实现,生产者和消费者分别处于不同的线程或进程中,由操作系统进行调度。来看一篇经典的多线程教程中的例子,是不是很像Java风格?—啰嗦。
MessageQueue方案1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import threading
import time
import logging
import random
import Queue
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-9s) %(message)s',)
BUF_SIZE = 10
q = Queue.Queue(BUF_SIZE)
class ProducerThread(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None):
super(ProducerThread,self).__init__()
self.target = target
self.name = name
def run(self):
while True:
if not q.full():
item = random.randint(1,10)
q.put(item)
logging.debug('Putting ' + str(item)
+ ' : ' + str(q.qsize()) + ' items in queue')
time.sleep(random.random())
return
class ConsumerThread(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None):
super(ConsumerThread,self).__init__()
self.target = target
self.name = name
return
def run(self):
while True:
if not q.empty():
item = q.get()
logging.debug('Getting ' + str(item)
+ ' : ' + str(q.qsize()) + ' items in queue')
time.sleep(random.random())
return
if __name__ == '__main__':
p = ProducerThread(name='producer')
c = ConsumerThread(name='consumer')
p.start()
time.sleep(2)
c.start()
time.sleep(2)
基于多线程方案,这个问题已经演变成消息中介模式(有些公司喜欢称之为”邮局”),有各种的商业MQ方案可以直接使用。
这里以RabbitMQ开源方案为例,Producer一方向名为队列中发送”Hello World!”内容,而Consumer一方则监听队列,当有内容进入队列时,就执行callback函数来收取并处理内容。发送与收取的动作是异步执行的,互不干扰。
(论坛帖子长度限制,后半段待续或见https://www.jackyshen.com/2015/0 ... th-python-yielding/)