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
继承式调用
class MyThread(threading.Thread): def __init__(self, n ):
super(MyThread, self).__init__()
self.n = n def run(self): # 必须写run函数
print(self.n)
time.sleep(2) t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start() print(t1.getName())
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 获得线程号
线程执行顺序
主线程启动子线程后,两者之间运行是并行的,默认主线程不会等待子线程运行结束,创建完成后继续往下执行,执行完后等待子线程全部执行完后退出程序。
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子线程。
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(5):
t = threading.Thread(target=run, args=('t-%s' %i ,))
t.setDaemon(True)
t.start()
thread_pool.append(t) for i in thread_pool:
i.join(0.5) # 有n个线程就等待n 倍的timeout print('------------------------------------------') print('run_time = ', time.time()- start_time) #
# running ('t-0',)
# running ('t-1',)
# running ('t-2',)
# running ('t-3',)
# running ('t-4',)
# ------------------------------------------
# run_time = 2.531254768371582
#
# Process finished with exit code 0
join_timeout1
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(5):
t = threading.Thread(target=run, args=('t-%s' %i ,))
t.setDaemon(True)
t.start()
thread_pool.append(t) for i in thread_pool:
i.join(0.6) # 有n个线程就等待n 倍的timeout print('------------------------------------------') print('run_time = ', time.time()- start_time) # #running ('t-0',)
# running ('t-1',)
# running ('t-2',)
# running ('t-3',)
# running ('t-4',)
# ('t-2',) is done
# ('t-1',) is done
# ('t-0',) is done
# ('t-4',) is done
# ('t-3',) is done
# ------------------------------------------
# run_time = 3.021475076675415
#
# 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' % n)
time.sleep(1)
print('[%s]------done----\n' % 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('\033[42;0mGreen is lighten...\033[0m')
elif counter < 10:
if events.isSet():
events.clear()
print('\033[41;0mRed is lighten...\033[0m') else:
counter = 0
print('\033[42;1m--green light on---\033[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("\033[32;1m[producer]\033[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:\D\program\Python354\python.exe C:/D/personal_data/workspace/四/day10/greenlet携程.py
#
#
#
#
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']
python-进程、线程与协程的更多相关文章
- python进程.线程和协程的总结
I.进程: II.多线程threading总结 threading用于提供线程相关的操作,线程是应用系统中工作的最小单位(cpu调用的最小单位). Python当前版本的多线程没有实现优先级,线程组, ...
- 11.python之线程,协程,进程,
一,进程与线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行 ...
- python系列7进程线程和协程
目录 进程 线程 协程 上下文切换 前言:线程和进程的关系图 由下图可知,在每个应用程序执行的过程中,都会去产生一个主进程和主线程来完成工作,当我们需要并发的执行的时候,就会通过主进程去生成一系列的 ...
- python简单线程和协程学习
python中对线程的支持的确不够,不过据说python有足够完备的异步网络框架模块,希望日后能学习到,这里就简单的对python中的线程做个总结 threading库可用来在单独的线程中执行任意的p ...
- Python基础线程和协程
线程: 优点:共享内存,IO操作时,创造并发操作 缺点:枪战资源 线程不是越多越好,具体案例具体分析,请求上下文切换耗时 IO密集型适用于线程,IO操作打开文件网络通讯类,不需要占用CPU,只是由CP ...
- Python(进程池与协程)
1.进程池与线程池: 为什么要用“池”:池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务 池子内什么时候装进程:并发的任务属于计算密集型 池子内什么时候装线程:并 ...
- Python(八)进程、线程、协程篇
本章内容: 线程(线程锁.threading.Event.queue 队列.生产者消费者模型.自定义线程池) 进程(数据共享.进程池) 协程 线程 Threading用于提供线程相关的操作.线程是应用 ...
- Python学习之路--进程,线程,协程
进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Q ...
- Python—进程、线程、协程
一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...
- python中socket、进程、线程、协程、池的创建方式和应用场景
进程 场景 利用多核.高计算型的程序.启动数量有限 进程是计算机中最小的资源分配单位 进程和线程是包含关系 每个进程中都至少有一条线程 可以利用多核,数据隔离 创建 销毁 切换 时间开销都比较大 随着 ...
随机推荐
- LinkedList简介
原文:https://blog.csdn.net/GongchuangSu/article/details/51527042 LinkedList简介 LinkedList 是一个继承于Abstrac ...
- 阶段3 2.Spring_09.JdbcTemplate的基本使用_6 JdbcDaoSupport的使用以及Dao的两种编写方式
复制三个出来.分别叫做 OrderDaoImpl.ProductDaoImpl.UserDaoImpl 复制这三个出来就是为了解决重复性代码的问题. 每个dao中都有这段代码.这些都是重复性的代码.在 ...
- 关于 if __name__ == '__main__':的使用
当是用 if __name__ == '__main__': 时,下面调用函数只是在当前脚本调试, 而我们需要在别处导入这个脚本中的类或者函数时,这个if __name__ == '__main__ ...
- CentOS mysql安装
MySQL For Excel 1.3.5MySQL for Visual Studio 1.2.5MySQL Fabric 1.5.6 & MySQL Utilities 1.5.6Conn ...
- django 数据库操作详解
Django配置使用mysql数据库 修改 settings.py 中的 DATABASES 注意:django框架不会自动帮我们生成mysql数据库,所以我们需要自己去创建. DATABASES ...
- C++统计程序运行时间代码片段
因为经常需要统计代码的运行时间,所以计时功能就显得很重要, 记录一下现在喜欢用的计时方式,供日后查阅. 1.下面是计时主函数, bool TimeStaticMine(int id,const cha ...
- DateTime相关
1.string数据转成datetime DateTimeFormatter forPattern = DateTimeFormat.forPattern("yyyyMMddHH" ...
- Python密码登录程序的思考--学与习
# 初学者的起步,对于开始的流程图结构还不太熟悉 # 思考: 1,write()与writelines()的区别,前者确定为字符串,后者为序列(列表,字典.元组等),自动为你迭代输入# ...
- Android 透明主题
转至:https://blog.csdn.net/zhangwenchaochao/article/details/78654128 Activity采用透明主题有两种方式: 重要的内容说三遍: 采用 ...
- mysql自增主键字段重排
不带外键模式的 mysql 自增主键字段重排 1.备份表结构 create table table_bak like table_name; 2.备份表数据 insert into table_bak ...