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()
gevent socket client

 

论事件驱动与异步IO  

  

  

  

  

 

  

 

posted @ 2016-10-14 09:11  a_monologue  阅读(83)  评论(0)    收藏  举报