python-进程、线程与协程

基础概念

进程

  是一个执行中的程序,即将程序装载到内存中,系统为它分配资源的这一过程。进程是操作系统资源分配的基本单位。

  每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。

    • 文本区域存储处理器执行的代码;
    • 数据区域存储变量和进程执行期间使用的动态分配的内存;
    • 堆栈区域存储着活动过程调用的指令和本地变量。

进程状态

进程与程序

  • 程序是指令数据的集合,是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念;
  • 程序和进程无一一对应关系,一个程序可由多个进程共用,一个进程在活动中又可顺序地执行若干个程序;
  • 进程是一个能独立运行的单位,能与其他进程并发执行,进程是作为资源申请和调度单位存在的;而通常的程序段不能作为一个独立运行的单位。

线程

  线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
  一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  python不管PC有几核,在同一核同一时刻执行的线程只有一个,python是调用系统的原生线程。

线程与进程

  • 进程要操作CPU,必须要先创建一个线程,所有在同一个进程里的线程是共享同一块内存空间的;线程共享内存空间,进程的内存是独立的;
  • 同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现;
  • 创建新线程很简单, 创建新进程需要对其父进程进行一次克隆;
  • 一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程。

同步与异步

  同步:是指一个进程在执行某个请求的时候,若这个请求没有执行完成,那么这个进程将会一直等待下去,直到这个请求执行完毕,才会继续执行下面的请求。

  异步:是指一个进程在执行某个请求的时候,如果这个请求没有执行完毕,进程不会等待,而是继续执行下面的请求。

并发与并行

  并发:计算机的操作系统通过时间片轮转法等算法调度交替执行不同的任务。

  并行:同时执行不同的任务。


多线程

线程调用方式

直接调用

import threading
import time

def run(*args):           # 线程要运行的函数

    print('test',args)
    time.sleep(3)

t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t1.start()
t2.start()

print(t1.getName())     # Thread-1
print(t2.getName())     # Thread-2

继承式调用

 1 class MyThread(threading.Thread):
 2 
 3     def __init__(self, n ):
 4         super(MyThread, self).__init__()
 5         self.n = n
 6 
 7     def run(self):                # 必须写run函数
 8         print(self.n)
 9         time.sleep(2)
10 
11 t1 = MyThread('t1')
12 t2 = MyThread('t2')
13 t1.start()
14 t2.start()
15 
16 print(t1.getName())
17 print(t2.getName())

线程方法

t.join(n)       表示主线程等待子线程多少时间,n表示主线程等待子线程的超时时间,如果在n时间内子线程未完成,主线程不在等待,执行后面的代码
t.run()         线程被cpu调度后自动执行线程对象的run方法(一般我们无需设置,除非自己定义类调用)
t.start()       线程准备就绪,等待CPU调度
t.getName()     获取线程的名称
t.setName()     设置线程的名称
t.name          获取或设置线程的名称
t.is_alive()    判断线程是否为激活状态
t.isAlive()     判断线程是否为激活状态
t.isDaemon()    判断是否为守护线程
t.setDaemon()                是否设置守护线程,True表示主线程不等待子线程全部完成就执行后面的代码,False默认值,标识主线程等待子线程全部执行完后继续执行后面的代码
threading.current_thread()   当前线程详细信息
threading.active_count()   当前活跃线程数
threading.get_ident          获得线程号
threading.enumerate()     当前执行的线程列表

线程执行顺序

  主线程启动子线程后,两者之间运行是并行的,默认主线程不会等待子线程运行结束,创建完成后继续往下执行,执行完后等待子线程全部执行完后退出程序。

import threading
import time
def run(*args):           # 线程要运行的函数
    print('test',args)
    time.sleep(3)
    print(args, 'is done')

start_time = time.time()
for i in range(3):
    t = threading.Thread(target=run, args=('t-%s' %i ,))
    t.start()
print("-------------------------------------------")
print('run_time = ', time.time()- start_time)

# test ('t-0',)
# test ('t-1',)
# test ('t-2',)
# -------------------------------------------
# run_time =  0.0
# ('t-2',) is done
# ('t-1',) is done
# ('t-0',) is done

  加入join,主线程会等待子线程执行完毕后继续往下执行,如果主线程需要子线程的返回结果,可以使用join。

import threading
import time
def run(*args):           # 线程要运行的函数
    print('running',args)
    time.sleep(3)
    print(args, 'is done')
start_time = time.time()
thread_pool = []
for i in range(2):
    t = threading.Thread(target=run, args=('t-%s' %i ,))
    t.start()
    thread_pool.append(t)

for i in thread_pool:
    i.join()
print('------------------------------------------')
print('run_time = ', time.time()- start_time)
# running ('t-0',)
# running ('t-1',)
# ('t-1',) is done
# ('t-0',) is done
# ------------------------------------------
# run_time =  3.0156474113464355

join

  join 参数:timeout 有n个设置join的子线程,就等待n倍timeout, 默认一直等待执行完毕

  当没有设置守护线程时,主线程等待 N 倍timeout后继续往下执行,主线程执行完毕,子线程依然可以继续执行,执行完毕后退出程序;对于守护线程则是到时间就kill子线程。

 1 import threading
 2 import time
 3 def run(*args):           # 线程要运行的函数
 4     print('running',args)
 5     time.sleep(3)
 6     print(args, 'is done')
 7 
 8 start_time = time.time()
 9 thread_pool = []
10 for i in range(5):
11     t = threading.Thread(target=run, args=('t-%s' %i ,))
12     t.setDaemon(True)
13     t.start()
14     thread_pool.append(t)
15 
16 for i in thread_pool:
17     i.join(0.5)           # 有n个线程就等待n 倍的timeout
18 
19 print('------------------------------------------')
20 
21 print('run_time = ', time.time()- start_time)
22 
23 #
24 # running ('t-0',)
25 # running ('t-1',)
26 # running ('t-2',)
27 # running ('t-3',)
28 # running ('t-4',)
29 # ------------------------------------------
30 # run_time =  2.531254768371582
31 #
32 # Process finished with exit code 0
join_timeout1
 1 import threading
 2 import time
 3 def run(*args):           # 线程要运行的函数
 4     print('running',args)
 5     time.sleep(3)
 6     print(args, 'is done')
 7 
 8 start_time = time.time()
 9 thread_pool = []
10 for i in range(5):
11     t = threading.Thread(target=run, args=('t-%s' %i ,))
12     t.setDaemon(True)
13     t.start()
14     thread_pool.append(t)
15 
16 for i in thread_pool:
17     i.join(0.6)           # 有n个线程就等待n 倍的timeout
18 
19 print('------------------------------------------')
20 
21 print('run_time = ', time.time()- start_time)
22 
23 # #running ('t-0',)
24 # running ('t-1',)
25 # running ('t-2',)
26 # running ('t-3',)
27 # running ('t-4',)
28 # ('t-2',) is done
29 # ('t-1',) is done
30 # ('t-0',) is done
31 # ('t-4',) is done
32 # ('t-3',) is done
33 # ------------------------------------------
34 # run_time =  3.021475076675415
35 # 
36 # Process finished with exit code 0
join_timeout2

守护线程

  为主线程服务,主线程退出,不必等待守护线程的结束。

  t.setDaemon(True)  把当前线程设置成守护线程 ,在t.start()之前设置

import threading
import time

def run(*args):           # 线程要运行的函数

    print('test',args)
    time.sleep(3)
    print(args, 'is done')

start_time = time.time()
for i in range(2):
    t = threading.Thread(target=run, args=('t-%s' %i ,))
    t.setDaemon(True)
    t.start()

print("-------------------------------------------")
print('run_time = ', time.time()- start_time)
# 
# test ('t-0',)
# test ('t-1',)
# -------------------------------------------
# run_time =  0.0
# Process finished with exit code 0

  由守护线程创建的子线程为守护线程,可以通过t.isDaemon来判断。

import time
import threading

def run(n):
    print('[%s]------running----
' % n)
    time.sleep(1)
    print('[%s]------done----
' % n)

def main():
    for i in range(2):
        t = threading.Thread(target=run, args=[i, ])
        t.start()
        print(i,t.isDaemon())
        t.join()

m = threading.Thread(target=main, args=[])
m.setDaemon(True)  # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务
m.start()
m.join(timeout=2)
print("---main thread done----")

# [0]------running----
# 0 True
# [0]------done----
# [1]------running----
# 1 True
# ---main thread done----

GIL

  无论启多少个线程,有多少个cpu,Python在执行的时候会淡定的在同一时刻只允许一个线程运行。

  GIL(Global Interpreter Lock)是在实现Python解析器(CPython)时所引入的一个概念,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。GIL并不是Python的特性,Python完全可以不依赖于GIL。

  GIL对python多线程的影响: http://www.dabeaz.com/python/UnderstandingGIL.pdf 

线程锁(互斥锁Mutex)

  线程锁保证同一时刻只有一个线程修改内存空间的同一数据,GIL保证同一时刻只有一个线程在运行。

  多线程同时修改同一数据,可能会导致数据最终结果不准确。

import time
import threading

def addNum():
    global num  # 在每个线程中都获取这个全局变量
    time.sleep(1)
    num -= 1  # 对此公共变量进行-1操作
    print('%s--get num:%s:'%(threading.current_thread().name,num ))

num = 5 # 设定一个共享变量
thread_list = []
for i in range(5):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待所有线程执行完毕
    t.join()

print('final num:', num)

  如果需要多个线程去修改同一数据,则需要给数据加一个线程锁。python3.x不加锁也不会出问题,但是建议加。如果修改数据量比较大的话,容易产生串行。

import time
import threading

lock = threading.Lock()

def addNum():
    lock.acquire()
    global num  # 在每个线程中都获取这个全局变量
    time.sleep(1)
    num -= 1  # 对此公共变量进行-1操作
    print('%s--get num:%s'%(threading.current_thread().name,num ))
    lock.release()

num = 5 # 设定一个共享变量
thread_list = []
for i in range(5):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待所有线程执行完毕
    t.join()

print('final num:', num)

递归锁

  一个锁里面包含了子锁。

import time
import threading

lock = threading.RLock()
def run2():
    global num2
    lock.acquire()
    num2 -= 1
    lock.release()

def addNum():
    global num1 # 在每个线程中都获取这个全局变量
    lock.acquire()
    run2()
    time.sleep(1)
    num1 -= 1  # 对此公共变量进行-1操作
    print('%s--get num1:%s,num2:%s'%(threading.current_thread().name,num1,num2))
    lock.release()

num1, num2 = 5, 9 # 设定一个共享变量
thread_list = []
for i in range(5):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

while threading.active_count() != 1:
    print(threading.active_count())
    time.sleep(1)
else:
    print('----all threads done---')
print('final num:', num1, num2)

信号量:Semaphore

  互斥锁允许同一时刻只有一个线程修改数据,Semaphore 允许同一时刻运行一定数量的线程。

import time
import threading

semaphore = threading.BoundedSemaphore(3)

def addNum():
    semaphore.acquire()
    time.sleep(1)
    print('%s---running'%threading.current_thread().name)
    semaphore.release()

for i in range(10):
    t = threading.Thread(target=addNum)
    t.start()

while threading.active_count() != 1:
    pass
else:
    print('----all threads done---')

Events

  红绿灯,一个线程充当交通指挥灯,多个线程充当车辆,按照红灯停绿灯行的规则。注意event.set, event.clear放在打印灯和车的状态位置,否则容易出现红灯车也在跑的情况。

  Event对象通过设置/清除标志位来实现和其他线程的同步,例如交通灯来修改Event的标志位来控制车辆线程的状态。

import threading, time, random

events = threading.Event()

def lighter():
    if not events.isSet():
        events.set()                        # 初始化绿灯Event set
    counter = 0
    while True:
        if counter < 5:
            print('33[42;0mGreen is lighten...33[0m')
        elif counter < 10:
            if events.isSet():
                events.clear()
            print('33[41;0mRed is lighten...33[0m')

        else:
            counter = 0
            print('33[42;1m--green light on---33[0m')
            events.set()
        time.sleep(1)
        counter += 1

def car(i):
    while True:
        if events.isSet():
            print("car[%s] is running..."%i)
            time.sleep(random.randrange(10))
        else:
            print('car is waiting green lighten...')
            events.wait()
if __name__ == '__main__':
    lighter1 = threading.Thread(target=lighter)
    lighter1.start()
    for i in range(3):
        t = threading.Thread(target=car, args=(i,))
        t.start()

  Event对象通过设置/清除标志位来实现和其他线程的同步,例如交通灯来修改Event的标志位来控制车辆线程的状态。
Event的几大方法:

event.set:       设置标志位为True
event.clear:   清除标志位,标志位为False
event.wait:    如果标志位为True,则不做操作;否则一直阻塞至标志位被设置为True
event.isSet:    相当于event.is_set,如果标志位被设置,则返回True;否则返回False

queue

  定义queue

class queue.Queue(maxsize=0)             # 先入先出
class queue.LifoQueue(maxsize=0)        # 后入先出
class queue.PriorityQueue(maxsize=0)  # 按设置优先级获取数据

maxsize:  代表队列最多能存放的条目数,一旦达到maxsize队列将会阻塞,直到队列被被使用;0或者负数表示队列无穷大
优先级原则:最低值的条目是先检索的,最低值的条目是排序后返回的条目(列表(条目))[0]。条目的典型模式是表单中的元组:(priority_number, data)。

常用方法

Queue.qsize()                #  返回队列大小
Queue.empty()                #  如果队列为empty,则返回True
Queue.full()                 #  如果队列为full,则返回True

Queue.put(item, block=True, timeout=None)
  # 将item插入队列,默认block为True, timeout为None,如果队列为full,则会阻塞知道队列条目被get。
  # 如果timeout是一个正数,将会等待timeout 秒,队列仍然为full,则抛出Full exception;
  # 如果block为false, 队列为full, 此时向队列插入数据时,直接抛出Full exception, 即使timeout设置为正数也将会被忽略。

Queue.put_nowait(item)       # 等价于 put(item, False)

Queue.get(block=True, timeout=None)   # 从队列中获取数据或者删除队列书中的数据,默认block为True,timeout为None,如果队列为空,则会阻塞直到队列为非空。 # 如果timeout是一个正数,将会等待timeout 秒,队列仍然为空,则抛出Empty exception;   # 如果block为false,队列为空,此时从队列获取数据时,直接抛出Empty exception,即使timeout设置为正数也将会被忽略。 Queue.get_nowait()       # 等价于 get(False)
exception queue.Empty exception queue.Full Queue.task_done() Queue.join() # block直到queue被消费完毕

参考: https://docs.python.org/3.5/library/queue.html#queue.Queue.task_done

多线程的应用

  python多线程不适合cpu密集操作型任务,适合io操作密集型的任务。

  io操作不占用cpu,计算占用cpu。


多进程

  多进程的定义和多线程类似。每个进程都拥有一个独立的内存空间,所以多进程需要较大的开销。

from multiprocessing import Process
import os

def run(i):
    print('number: %s, process id: %s, parent id: %s, moudle name: %s'%
          (i, os.getpid(),os.getppid(),__name__))

p_num = []
if __name__ == '__main__':     # 多进程win 系统需要加这句,否则报错
    for i in range(10):
        p = Process(target=run, args=(i,))
        p.start()
        p_num.append(p)

    for p in p_num:
        p.join()               # 和多线程功能一样

    print('main is done, moudle name: %s'%__name__)
number: 0, process id: 988, parent id: 25368, moudle name: __mp_main__
number: 1, process id: 5240, parent id: 25368, moudle name: __mp_main__
number: 3, process id: 21328, parent id: 25368, moudle name: __mp_main__
number: 2, process id: 24892, parent id: 25368, moudle name: __mp_main__
number: 4, process id: 25276, parent id: 25368, moudle name: __mp_main__
number: 5, process id: 24612, parent id: 25368, moudle name: __mp_main__
number: 7, process id: 12340, parent id: 25368, moudle name: __mp_main__
number: 6, process id: 24464, parent id: 25368, moudle name: __mp_main__
number: 8, process id: 20040, parent id: 25368, moudle name: __mp_main__
number: 9, process id: 20796, parent id: 25368, moudle name: __mp_main__
main is done, moudle name: __main__

Process finished with exit code 0
run_result

进程间通信

  同一进程内的线程共享进程内存空间,可以直接访问同一数据;不同进程都拥有一个独立内存空间,要想实现两个进程间的数据交换,可通过以下方法:Queue,Pipe,Manager。

Queue

  使用方法和线程queue一样,线程中的queue不能实现进程中的数据通信。

from multiprocessing import Process, Queue
import os

def run(q):
    print('process id: %s'% os.getpid())
    q.put(os.getpid())

p_num = []
if __name__ == '__main__':     # 多进程win 系统需要加这句,否则报错
    q = Queue()
    for i in range(10):
        p = Process(target=run, args=(q,))
        p.start()
        p_num.append(p)
    for p in p_num:
        p.join()               # 和多线程功能一样

    for i in range(10):
        print('main is done, q = %s'% q.get())
process id: 12976
process id: 27556
process id: 11424
process id: 28284
process id: 29056
process id: 14728
process id: 22856
process id: 26928
process id: 28496
process id: 6612
main is done, q = 27556
main is done, q = 12976
main is done, q = 11424
main is done, q = 28284
main is done, q = 29056
main is done, q = 14728
main is done, q = 22856
main is done, q = 26928
main is done, q = 28496
main is done, q = 6612

Process finished with exit code 0
result

Pipe

  由Pipe()返回的两个连接对象表示管道的两端,每个连接对象都有send()和recv()方法。
  注意,如果两个进程(或线程)试图同时读取或写入管道的同一端口,那么管道中的数据可能会被损坏;在同时使用不同端口的过程中也不会有风险。

from multiprocessing import Process, Pipe
import  os

def run(conn):
    conn.send('process id: %s'%os.getpid())
    conn.send('process done')
    conn.close()
    print('child process id: %s'% os.getpid())

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=run, args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    print(parent_conn.recv())

# child process id: 10816
# process id: 10816
# process done

Manager

  Manager 支持list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array等数据类型。

from multiprocessing import Process, Manager
import os

def f(d, l):
    d['%s parent is %s'%(os.getpid(), os.getppid())] = os.getppid()
    l.append(os.getpid())

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list(range(3))
        p_list = []
        for i in range(5):
            p = Process(target=f, args=(d, l))
            p.start()
            p_list.append(p)
        for res in p_list:
            res.join()

        print(list(d.keys()))    # 以list类型返回字典的key
        print(l)
        
# ['26500 parent is 29096', '23932 parent is 29096', '29540 parent is 29096', '27728 parent is 29096', '15348 parent is 29096']
# [0, 1, 2, 26500, 27728, 15348, 23932, 29540]

进程同步:进程锁

  进程锁用来锁定输出,否则容易混淆,比如一个进程输出没完,另一个进程继续输出。

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print('hello world', i)
    l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

进程池

  进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

apply          # 从进程池中获取的进程串行运行
apply_async   # 从进程池中获取的进程并行运行

实例

from multiprocessing import Pool
import time, os

def f1(arg):
    time.sleep(1)
    print('process id %s:'%os.getpid(),arg)
    return arg     # 传给Bar

def Bar(args):      # 由父进程执行
    print('%s execute %s'%(os.getppid(),args))

if __name__ == '__main__':
    pool = Pool(5)
    for i in range(10):
        # pool.apply(func=f1,args=(i,))       # 所有进程串行执行
        pool.apply_async(func=f1, args=(i,), callback=Bar)  # 异步并行执行

    pool.close()                         # 等待所有的任务执行完毕

    # pool.terminate()                  # 立即终止子进程的任务,主进程继续执行
    pool.join()  # 执行pool.join时必须先执行pool.close或者pool.terminate。进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭close,terminate也无效
    print('end')
process id 12704: 0
10608 execute 0
process id 3440: 1
10608 execute 1
process id 656: 2
10608 execute 2
process id 14652: 3
10608 execute 3
process id 15072: 4
10608 execute 4
process id 12704: 5
process id 3440: 6
10608 execute 5
10608 execute 6
process id 656: 7
10608 execute 7
process id 14652: 8
10608 execute 8
process id 15072: 9
10608 execute 9
end

Process finished with exit code 0
result

协程

协程,又称微线程,纤程,是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态(进入上一次离开时所处逻辑流的位置)

协程与线程类似,每个协程表示一个执行单元,既有自己的本地数据,也与其他协程共享全局数据和其他资源。

协程存在于线程中,需要用户来编写调度逻辑,对CPU而言,不需要考虑协程如何调度,切换上下文。

优点

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 高并发+高扩展性+低成本

  "原子操作(atomic operation)是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换 (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。

不足

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

yield

  python通过yield提供了对协程的基本支持,但是并不完全。

import time
import queue

def consumer(name):
    print("--->starting eating baozi...")
    while True:
        new_baozi = yield
        print("[%s] is eating baozi %s" % (name, new_baozi))
        # time.sleep(1)

def producer():
    input() 
    r = con.__next__()
    r = con2.__next__()
    n = 0

    while n < 5:
        n += 1
        con.send(n)
        con2.send(n)
        print("33[32;1m[producer]33[0m is making baozi %s" % n)

if __name__ == '__main__':
    con = consumer("c1")
    con2 = consumer("c2")
    p = producer()

greenlet

  greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator。

  gevent对协程的支持,本质上是greenlet在实现切换工作。

  greenlet的工作流程:进行访问网络的IO操作时,出现阻塞,greenlet就显式切换到另一段没有被阻塞的代码执行,直到原来的阻塞状况消失以后,再切换回原来代码段继续处理。因此,greenlet是一种合理安排的串行方法。

  greenlet.switch()可实现协程的切换,greenlet并不能实现自动切换。

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()
def test2():
    print(56)
    gr1.switch()
    print(78)

gr1 = greenlet(test1) #启动一个携程
gr2 = greenlet(test2)
gr1.switch()

# C:DprogramPython354python.exe C:/D/personal_data/workspace/四/day10/greenlet携程.py
# 12
# 56
# 34
# 78

gevent

  gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程。gevent是对greenlet进行封装,实现协程的自动切换。

  通过gevent.sleep模仿IO操作,实现协程的切换。

  gevent.spawn    用来形成协程

  gevent.joinall     添加这些协程任务,并且执行给定的gevent,同时阻塞当前程序流程,当所有gevent执行完毕程序继续向下执行

  gevent.sleep     模拟IO操作多少时间

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(2)
    print('Explicit context switch to foo again')
def bar():
    print('Explicit精确的 context内容 to bar')
    gevent.sleep(1)
    print('Implicit context switch back to bar')
def func3():
    print("running func3 ")
    gevent.sleep(0)
    print("running func3  again ")

gevent.joinall([
    gevent.spawn(foo), #生成,
    gevent.spawn(bar),
    gevent.spawn(func3),
])

# Running in foo
# Explicit精确的 context内容 to bar
# running func3 
# running func3  again 
# Implicit context switch back to bar
# Explicit context switch to foo again
# 
# Process finished with exit code 0

  上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。

  初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。

  执行流程只会在 所有greenlet执行完后才会继续向下走。

monkey.patch_all()

  协程切换是在IO操作时自动完成,在启动时通过monkey.patch_all()实现将一些常见的阻塞,如socket,select,urllib等地方实现协程跳转,因为gevent并不能完全识别所有当前操作是否为IO操作,而未切换。

import gevent
import requests

def run_task(url):
    print('Visit --> %s'% url)
    try:
        res = requests.get(url)
        data = res.text
        print('%s bytes received from %s' %(len(data),url))
    except Exception as value:
        print(value)

if __name__ == '__main__':
    urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/']
    gevents = [ gevent.spawn(run_task, url) for url in urls]
    gevent.joinall(gevents)
#     
# Visit --> https://www.baidu.com/
# 2443 bytes received from https://www.baidu.com/
# Visit --> https://github.com/
# 54833 bytes received from https://github.com/
# Visit --> https://www.python.org/
# 48703 bytes received from https://www.python.org/

  monkey.patch_all() 相当于把当前程序的所有的io操作单独做上标记,完成自动切换。

from gevent import monkey; monkey.patch_all()
import gevent
import requests

def run_task(url):
    print('Visit --> %s'% url)
    try:
        res = requests.get(url)
        data = res.text
        print('%s bytes received from %s' %(len(data),url))
    except Exception as value:
        print(value)

if __name__ == '__main__':
    urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/']
    gevents = [ gevent.spawn(run_task, url) for url in urls]
    gevent.joinall(gevents)

# Visit --> https://www.baidu.com/
# Visit --> https://github.com/
# Visit --> https://www.python.org/
# 2443 bytes received from https://www.baidu.com/
# 54833 bytes received from https://github.com/
# 48703 bytes received from https://www.python.org/

  从上可以看出,没有monkey.path_all的情况无切换相当于串行,patch之后遇到IO操作自动切换,3个网络操作时并发执行,结束顺序不同,但其实只有一个线程。

gevent应用

  gevent实现并发socket

# server
import socket
import gevent
from gevent import monkey; monkey.patch_all()

def handler(conn):
    try:
        while True:
            data = conn.recv(1024).decode()
            if len(data):
                print("receive: %s"%data)
                conn.send(data.upper().encode())
    except Exception as value:
        print(value)
HOST = ('0.0.0.0',9999)
server = socket.socket()
server.bind(HOST)
server.listen()
print('server start...')
while True:
    conn,addr = server.accept()
    print('receive connection:%s'% conn)
    gevent.spawn(handler,conn)

 # client
import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('localhost',9999))
while True:
    data = input('>>>').strip()
    client.send(data.encode())
    data = client.recv(1024)
    print('receive: %s' %data)

  gevent还提供对池的支持,当拥有动态数量的greenlet需要进行并发管理(限制并发数)时,就可以使用池,在处理大量的网络或IO操作时非常重要。

from gevent import monkey; monkey.patch_all()
from gevent.pool import Pool
import requests

def run_task(url):
    print('Visit --> %s'% url)
    try:
        res = requests.get(url)
        data = res.text
        print('%s bytes received from %s' %(len(data),url))
    except Exception as value:
        print(value)
    return 'url:%s --> finished' % url

if __name__ == '__main__':
    urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/']
    pool = Pool(2)
    result = pool.map(run_task, urls)
    print(result)

Visit --> https://www.baidu.com/
Visit --> https://github.com/
2443 bytes received from https://www.baidu.com/
Visit --> https://www.python.org/
54833 bytes received from https://github.com/
48703 bytes received from https://www.python.org/
['url:https://www.baidu.com/ --> finished', 'url:https://github.com/ --> finished', 'url:https://www.python.org/ --> finished']
 

 


 

原文地址:https://www.cnblogs.com/gareth-yu/p/8887969.html