Dayday up ---- python Day10
线程 vs 进程
进程是系统资源的集合
线程是一串指令的集合,包含在进程中,操作系统最小的调度单位
其实是没有可比性的
进程之间是独立的,但是线程内存共享,线程同时修改同一份数据时必须加锁,mutux互斥锁
原生进程,是由操作系统自己维护的
python 多线程不适合cpu密集操作型任务,适合io操作密集型的任务
多进程
(多进程使用和多线程差不多)
# --*--coding:utf-8--*--
import multiprocessing # 多进程模块
import time,threading
def thread_run():
print(threading.get_ident()) # 当前线程id
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',)) # 创建进程实例
p.start()
linux 上每一个进程都是由父进程启动的
# --*--coding:utf-8--*--
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[31m;1m called from child process function f\033[0m')
print("hello",name)
if __name__ == '__main__':
info('\033[32;1m main process line \033[0m')
p = Process(target=f, args=('bob',))
p.start()
不同进程间内存是不共享的,要想实现两个进程间的数据交换可以用以下方法:
1、线程里面的queue在线程中作用,如果想在两个进程中通信,需要用Queue
# --*--coding:utf-8--*--
from multiprocessing import Process,Queue
def f(q):
print("in child:", q.qsize()) # 返回队列大小
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
q.put("test123")
print(q.get())
p = Process(target=f, args=(q,))
p.start()
# print('hahaha,',q.get())
p.join()
print("last:", q.get_nowait()) # 不等待 = q.get(False)
2、pipe 管道
# --*--coding:utf-8--*--
from multiprocessing import Process,Pipe
def f(conn): # 子进程
conn.send([42, None, 'hello from child'])
conn.send([42, None, 'hello from child2'])
print("from parent:",conn.recv())
conn.close()
if __name__ == '__main__': # 父进程
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv())
print(parent_conn.recv())
parent_conn.send("hahaha")
p.join()
# if __name__ == '__main__':
# 作用是为了区分脚本的调用状态,如果是主动/手动调用就执行,如果从别的代码导入就不执行
3、managers 可以同时修改,managers 自动加锁了
# --*--coding:utf-8--*--
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)
虽然每个进程之间是独立的,但是它们共享是一个屏幕(打印),会杂乱,进程里面也有一把锁,Lock
# --*--coding:utf-8--*--
from multiprocessing import Process,Lock
def f(l, i):
l.acquire() # 加锁
try:
print('hello world',i)
finally:
l.release() # 解锁
if __name__ == '__main__':
lock = Lock()
for num in range(100):
Process(target=f,args=(lock, num)).start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止
进程池中有两个方法:
- apply 串行 同步
- apply_async 并行 异步
同步和异步:
同步: 就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去
异步: 是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态,当有消息返回的时候系统会通知进程进行处理,这样可以提高执行效率
# --*--coding:utf-8--*--
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__':
pool = Pool(processes=3) # 允许进程池同时放入3个进程
print("主进程:",os.getpid())
for i in range(10):
pool.apply_async(func=Foo, args=(i,),callback=Bar)
# callback 回调, 执行完func 再执行callback ,是主进程调用的回调,为了节省资源
print('end')
pool.close()
pool.join() # 进程池中的进程执行完毕后再关,如果注释,那么程序直接关闭
协程 Gevent
微线程, 协程是一种用户态的轻量级线程,cpu只认识线程
拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
协程能保留上一次调用的状态,(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法: 进入上一次离开时所处逻辑流的位置
协程的好处: 在单线程执行,串行
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化
nginx默认是单线程,一个线程上万个并发
缺点:
无法利用多核资源: 协程的本质是一个单线程,它不能同时将单个cpu的多个核用上,协程需要和进程配合才能运行在多cpu上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用
进行阻塞操作会阻塞掉整个程序
cpu 遇到io操作就切换,剩下就只有cpu运算,io操作完了就切回去
之前yield是我们自己写的协程,greenlet 是封装好了的协程,而greenlet 是手动切换,gevent 是自动切换
greenlet
# --*--coding:utf-8--*--
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()
gevent
是第三方库,可以轻松通过gevent 实现并发同步或异步编程,在gevent中用到的主要模式greenlet,它是以c扩展模块形式接入python的轻量级协程, Greenlet 全部运行在主程序操作系统进程的内部,但它们被协作式地调度.
# --*--coding:utf-8--*--
import gevent
def func1():
print("running func1")
gevent.sleep(2)
print("Explicit context switch to func1 again")
def func2():
print("running func2")
gevent.sleep(1)
print("Explicit context switch to func2 again")
def func3():
print("running func3")
gevent.sleep(0)
print("running func3 again")
gevent.joinall([
gevent.spawn(func1),
gevent.spawn(func2),
gevent.spawn(func3),
])
使用gevent做一个小爬虫
遇到IO阻塞时会自动切换任务
# --*--coding:utf-8--*--
from urllib import request
import gevent,time
from gevent import monkey
monkey.patch_all() # 把当前程序的所有io操作给单独的做上标记
def f(url):
print('GET: %s' % url)
resp = request.urlopen(url)
data = resp.read()
with open("url.html","wb") as file: # 下载网页
file.write(data)
print("%d bytes received from %s" % (len(data),url))
urls = [
# 'https://www.baidu.com/',
'https://www.tinydonuts.cn/'
]
# 同步
time_start = time.time()
for url in urls:
f(url)
print("同步cost",time.time() - time_start)
# 异步
async_time_start = time.time()
gevent.joinall([
# gevent.spawn(f, 'https://www.baidu.com/'),
gevent.spawn(f, 'https://www.tinydonuts.cn')
])
print("异步cost",time.time()-async_time_start )
# 结果
# GET: https://www.tinydonuts.cn/
# 2247 bytes received from https://www.tinydonuts.cn/
# 同步cost 0.3150179386138916
# GET: https://www.tinydonuts.cn
# 2247 bytes received from https://www.tinydonuts.cn
# 异步cost 0.12700724601745605
gevent socket server
# --*--coding:utf-8--*--
import sys
import socket,time,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
# --*--coding:utf-8--*-- import socket HOST = 'localhost' PORT = 8001 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True: msg = bytes(input(">>:"),encoding="utf-8") s.sendall(msg) data = s.recv(1024) print('Received', repr(data).upper()) s.close()
论事件驱动与异步IO

浙公网安备 33010602011771号