python 进程,线程,协程篇 II

进程,线程,协程篇 II

多进程

  1. 多进程基础知识
  2. 进程间通讯的三种方式
    • Queues
    • Pipes
    • Managers
    • 进程同步
  3. 进程池

协程

  1. 协程基础知识
  2. 使用 yield 实现协程操作例子
  3. Greenlet
  4. Gevent
  5. 遇到io阻塞时自动切换任务
  6. 并发爬虫
  7. 通过 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()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值