进程,线程,协程篇 II
多进程
- 多进程基础知识
- 进程间通讯的三种方式
- Queues
- Pipes
- Managers
- 进程同步
- 进程池
协程
- 协程基础知识
- 使用 yield 实现协程操作例子
- Greenlet
- Gevent
- 遇到io阻塞时自动切换任务
- 并发爬虫
- 通过 gevent 实现单线程下的多 socket 并发
多进程
1,多进程基础知识
概念
multiprocessing是一个使用类似于线程模块的API支持产生进程的包。
多处理包提供本地和远程并发,通过使用子进程而不是线程有效地侧向执行全局解释器锁。
因此,多处理模块允许程序员充分利用给定机器上的多个处理器。 它可以在Unix和Windows上运行。
多进程代码实现
import multiprocessing
import time,threading
def thread_run():
print(threading.get_ident())
def run(name):
time.sleep(2)
print("hello",name)
t = threading.Thread(target=thread_run,)
t.start()
if __name__ == '__main__':
for i in range(10):
p = multiprocessing.Process(target=run,args=('bob %s'%i,))
p.start()
# p.join()
获取多进程id
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
print("\n\n")
def f(name):
info('\033[31;1mfunction f\033[0m')
print('hello', name)
if __name__ == '__main__':
info('\033[32;1mmain process line\033[0m')
p = Process(target=f, args=('bob',))
p.start()
p.join()
2,进程间通讯的三种方式
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法: Queues,Pipes,Managers
Queues 队列
使用方法类似于 threading 中的 queue
# 进程queue 数据的传递
from multiprocessing import Process,Queue
import threading
# import queue
def f(q):
q.put([42,None,'hello'])
# def f(qq):
# print("in child:",qq.qsize())
# qq.put([42,None,'hello'])
if __name__ =='__main__':
q = Queue()
# p = threading.Thread(target=f,)
p = Process(target=f,args=(q,))
p.start()
print(q.get())
p.join()
Pipes 管道
Pipe()函数返回一个由管道连接的连接对象,默认情况下是双工(双向)
# pipe 管道
from multiprocessing import Process,Pipe
def f(conn):
conn.send([42,None,'hello from child'])
conn.send([42,None,'hello from child2'])
print("",conn.recv())
conn.close()
if __name__ =='__main__':
parent_conn,child_conn = Pipe()
p = Process(target=f,args=(child_conn,))
p.start()
print("parent",parent_conn.recv())
print("parent",parent_conn.recv())
parent_conn.send("from parent")
p.join()
Managers 管理器
Manager()返回的管理器对象控制一个服务器进程,该进程保存Python对象并允许其他进程使用代理操作它们。
Manager()返回的管理器将支持类型列表,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,
Event,Barrier,Queue,Value和Array
# manager 2 多个进程间进行数据的共享和传递
from multiprocessing import Process,Manager
import os
def f(d,l):
d[os.getpid()] = os.getpid()
l.append(os.getpid())
print(l)
if __name__ =='__main__':
with Manager() as manager:
d = manager.dict() #生成一个字典,可在多个进程间共享和传递
l = manager.list(range(5)) #生成一个列表,可在多个进程间共享和传递
p_list = []
for i in range(10):
p = Process(target=f,args=(d,l))
p.start()
p_list.append(p)
for res in p_list: #等待结果
res.join()
print(d)
print(l)
进程同步(进程的锁)
不使用来自不同进程的锁输出容易被混淆。
def f(l, i):
l.acquire()
try:
print('hello world', i)
finally:
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
3,进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,
如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
apply 串行
apply_async 异步
from multiprocessing import Process,Pool,freeze_support
import time,os
def Foo(i):
time.sleep(2)
print("in process",os.getpid())
return i+100
def Bar(arg):
print("--> exec done: ",arg,os.getpid())
if __name__ =='__main__':
#freeze_support()
pool = Pool(processes=5) #允许进程池同时放入5个进程
print("主进程",os.getpid())
for i in range(10):
pool.apply_async(func=Foo,args=(i,),callback=Bar)# apply_async异步执行,callback回调
# pool.apply(func=Foo,args=(i,)) #串行
pool.apply_async(func=Foo,args=(i,)) #异步执行
print('end')
pool.close()
pool.join() # 进程池中继承之行完毕后在关闭,如果注释,那么程序直接关闭
协程
1,协程基础知识
协程的概念:
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,
恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,
换种说法:进入上一次离开时所处逻辑流的位置。
协程的优缺点:
优点:
无需线程上下文切换的开销
无需原子操作锁定及同步的开销,原子操作是指不会被线程调度机制打断的操作
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.
当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
协程的满足条件:
1,必须在只有一个单线程里实现并发
2,修改共享数据不需加锁
3,用户程序里自己保存多个控制流的上下文栈
4,一个协程遇到IO操作自动切换到其它协程
2,使用 yield 实现协程操作例子
import time,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():
r = con.__next__()
r = con2.__next__()
n = 0
while n<5:
n += 1
con.send(n)
con2.send(n)
time.sleep(1)
print("\033[32;1m[producer]\033[0m is making baozi %s"%n)
if __name__ == '__main__':
con = consumer("c1")
con2 = consumer("c2")
p = producer()
3,Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,
它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
# 使用 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()
4,Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,
在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。
Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
# 使用 gevent 完成 自动io切换
import gevent
def foo():
print("running in foo")
gevent.sleep(2)
print("Exception context switch to foo again")
def bar():
print("Explict 精确的 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),
])
5,遇到io阻塞时自动切换任务
from gevent import monkey; monkey.patch_all()
import gevent
from urllib.request import urlopen
def f(url):
print('GET: %s' % url)
resp = urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
6,并发爬虫
# 异步爬虫
from urllib import request
import gevent,time,pickle
from gevent import monkey
monkey.patch_all() # 把当前程序的所有io操作都单独的做上标记
def f(url):
print('GET:%s'%url)
resp = request.urlopen(url)
data = resp.read()
f = open("test.txt", "wb") #数据存放
# print(json.dumps(info))
f.write(pickle.dumps(data))
f.close()
print("%d bytes received from %s."%(len(data),url))
# urls = ['https://www.python.org/',
# 'https://www.yahoo.com/',
# 'https://github.com/' ]
urls = ['https://www.python.org/']
time_start = time.time()
for url in urls:
f(url)
print("同步cost time",time.time()-time_start)
#异步操作
async_time_start = time.time()
# gevent.joinall([
# gevent.spawn(f, 'https://www.python.org/'),
# gevent.spawn(f, 'https://www.yahoo.com/'),
# gevent.spawn(f, 'https://github.com/'),
# ])
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/')
])
print("异步cost time",time.time()-async_time_start)
7,通过 gevent 实现单线程下的多 socket 并发
gevent_socket_server
# 通过 gevent 实现socket 并发
import sys
import socket
import time
import gevent
from gevent import socket, monkey
monkey.patch_all()
def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(8001)
gevent_socket_client
import socket
HOST = 'localhost' # The remote host
PORT = 9999 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"), encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
#
print('Received', data)
s.close()