欢迎来到Louis的博客

人生三从境界:昨夜西风凋碧树,独上高楼,望尽天涯路。 衣带渐宽终不悔,为伊消得人憔悴。 众里寻他千百度,蓦然回首,那人却在灯火阑珊处。
扩大
缩小

python 进程,IO多路复用,协程

上篇内容回顾:

1. GIL锁

GIL的叫做全局解释器锁,用于限制一个进程中在一个时间点只有一个线程能够执行(获取到GIL的线程),语言初期设计的初衷是用于维护数据安全,但是在现有的硬件环境下,GIL锁在执行计算密集型运算时效率会导致效率下降(较其他支持多线程语言来说)。

2. 进程和线程的区别

进程是cpu资源分配的最小单元。
线程是cpu计算的最小单元。
一个进程中可以有多个线程,默认有一个主线程。
对于python来说它的进程和线程和其他语言有差异,是有GIL锁。
GIL锁限制了一个进程中同一时刻只有一个线程能在CPU中执行。
注意:IO密集型操作可以使用多线程;计算密集型可以使用多进程

3.Lock 和 RLock

同步锁和递归锁

递归锁能够解决同步锁中出现死锁的问题

4.线程池

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(10)
pool.submit(func, arg1, arg2)
限制线程开启的数量
线程过多会导致大量的线程上下文切换,会消耗资源,导致cpu性能下降。

5. threading.local

local = threading.local()
为每一个线程开辟一个私有空间,存放线程独有的变量。

6. 线程常用方法

setName (线程名称) 给线程定义一个自定义名称
setDaemon(bool) 给子线程设置守护线程,参数默认值是False,当为True时则是主线程执行完之后,所有正在运行的子线程也全部强制关闭
start 启动线程
join(int) 给予参数(int类型),表示主线程等待子线程多少秒,不给参数,默认是子线程执行完后,才执行后续主线程代码。

t = threading.current_thread() 在线程要执行的方法中运行,获取当前的线程对象,t为当前的线程对象
t.getName() 获取线程的名字

 

进程

 


1.什么是进程

进程是计算机中的最小的资源分配单位,我们编写的代码是存放在硬盘上的,在代码运行起来的时候就会产生进程,进程就是我们代码的实现的一个实体。

进程和线程的几点区别:
  1.进程进行资源分配,线程才是执行代码的实体,进程是线程的容器。
  2.不同进程中的数据是相互隔离的,而同进程下的多个线程是共享进程下的数据的。

进程与线程的选择取决以下几点:
  1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
  2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
  3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
  4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
  5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

2.如何创建一个进程

跟创建线程的方法几乎一样,也有两种方式:

实例化一个multiprocess.Process 的对象:

import multiprocessing


def task(args):
    print(args)


for i in range(10):     # 创建10个进程
    t = multiprocessing.Process(target=task, args=(i,))
    t.start()

创建一个类继承multiprocess.Process,重写run方法:

import multiprocessing


class P(multiprocessing.Process):
    def __init__(self, target, args=(), kwargs={}):
        super().__init__()
        self.target = target
        self.args = args
        self.kwargs = kwargs

    def run(self):
        self.target(*self.args, **self.kwargs)


def task(args):
    print(args)


for i in range(10):
    m = P(target=task, args=(i,))
    m.start()

 

3.进程常用方法

m.daemon(bool)              设置主进程为子进程守护进程

m.name                 设置进程的名字

start()                启动进程

m.join()                  主进程等待子进程执行完成后继续执行

m1 = mutiprocessing.current_process()    获取方法当前运行进程的对象

m1.ident/pid                获取当前进程的id

import multiprocessing
import time


def func(arg):
    time.sleep(2)
    m = multiprocessing.current_process()  #获取当前方法所在线程的对象
    print(m.name)                #获取线程对象的名字
    print(m.ident, m.pid)            #获取线程的id
    print(arg)


for i in range(10):
    p = multiprocessing.Process(target=func, args=(i,))
    p.daemon = False              # 只能在进程没有执行之前设置守护进程,当为True时设置,将主进程设置为子进程的守护进程,当主进程销毁时,所有设置守护进程的子线程全都一起强制销毁 
    p.name = '进程%s' % i
    p.start()
    p.join()

print(123)

 

4.创建进程池

创建进程池和线程池的方法基本也是一样

import time
from concurrent.futures import ProcessPoolExecutor


def task(args):
    time.sleep(1)
    print(args)


pool = ProcessPoolExecutor(5)
for i in range(50):
    pool.submit(task, i)

5.进程的数据共享

同台服务器上进程之间默认是进程之间数据是相互隔离的,进程之间的数据共享在业务场景中一般使用第三方中间件来完成(redis,mongodb等),但python也提供了几种方式在代码里直接实现进程间数据共享。

使用进程模块提供的Queue类来实现,multiprocessing中实现了跟模块queue的Queue功能一样的Queue,使用它能实现进程之间的数据共享。

import time
from concurrent.futures import ProcessPoolExecutor

queue = multiprocessing.Queue()


def task(args):
    queue.put(args)
    print(args)
    time.sleep(2)


pool = ProcessPoolExecutor(5)
for i in range(10):
    pool.submit(task, i)


while True:
    try:
        v = queue.get(timeout=2)
        print(v)
    except Exception:
        break


使用multiprocessing的Manager类来实现数据共享,使用该类对象创建一个特殊的字典就可以用于进程之间线程的共享了。

import multiprocessing
from concurrent.futures import ProcessPoolExecutor

m_d = multiprocessing.Manager()
dic = m_d.dict()  # 普通的字典无法实现进程数据共享的功能,用Manager可以创建一个支持进程之间数据共享的字典


def task(arg):
    dic[arg] = arg
    print(dic)


pool = ProcessPoolExecutor(5)

for i in range(10):
    pool.submit(task, i)
m = multiprocessing.Process(target=task, args=(i,))
m.start()
m.join()

print(dic)

 IO多路复用

1. 什么叫IO多路复用?

 I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。
在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流, (学过EE的人现在可以站出来义正严辞说这个叫“时分复用”了)。

 

 

一个请求到来了,nginx使用epoll接收请求的过程是怎样的?, 多看看这个图就了解了。提醒下,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。

单个线程中管理多个IO操作对象就叫IO多路复用
IO多路复用:检测多个socket是否发生变化(是否已经完成连接/是否接收到数据)(可写,可读)

2. 异步非阻塞模型(socket+IO多路复用+回调函数)

操作系统检测socket是否发生变化,有三种模式(操作系统)
select: 限制监听的socket的个数,上限是1024个;循环检测
poll: 不限制监听的socket的个数;循环检测(水平触发)
epoll: 不限制监听的socket的个数;回调方式(边缘触发)

import select
import socket

key_list = ['sx', 'sb', 'db']


class Rep(object):
    '''
    将socket封装,使新的对象拥有一个回调函数
    '''
    def __init__(self, sk, func):
        '''

        :param sk:  socket对象
        :param func:    回调函数
        '''
        self.sk = sk
        self.func = func

    def fileno(self):
        '''
        由于select模块是调用fileno来获取socket的唯一标识,在对象中定义一个fileno供select调用
        :return:
        '''
        return self.sk.fileno()


class Nb(object):
    def __init__(self, url, key_list):
        self.socket = []
        self.conn = []
        self.url = url
        self.key_list = key_list

    def add(self, url, func):
        '''
        创建socket
        :param url:
        :param func:
        :return:
        '''
        client = socket.socket()
        client.setblocking(False)       #设置线程非阻塞, 实现非阻塞,
        try:
            client.connect((url, 80))   # 设置非阻塞在connect和recv连接是会抛出异常,此处暂时捕获不用处理
        except BlockingIOError as e:
            pass
        obj = Rep(client, func)         #对socket进行封装
        self.socket.append(obj)         #要检测的
        self.conn.append(obj)

    def run(self):
        '''
        使用select来进行socket监控并进行处理,select中由于遇到io操作就会进行切换,实现并发
        :return:
        '''
        while True:
            #   select.select的原理是循环检测self.socket, self.conn, []这三个列表中的socket状态,如果列表中的数据发生变化则把变化的数据返回
            #   r_list, w_list, e_list r_list为已经返回数据的socket的列表,socket状态为可读,w_list为socket连接成功的列表,socket状态为可写,e_list为socket出现异常的列表
            r_list, w_list, e_list = select.select(self.socket, self.conn, [], 0.05)    # 最多最多检测0,05秒
            for el in w_list:               # 对已连接的socket进行发送数据操作
                el.sk.sendall(
                    ('GET /s?wd=%s HTTP/1.0\r\nhost:%s\r\n\r\n' % (self.key_list.pop(0), self.url)).encode('utf-8'))
                self.conn.remove(el)        # 连接操作已完成,从conn列表中删除
            for item in r_list:             # 对已经返回数据的socket进行接收数据
                chunk_list = []
                while True:
                    try:                    # 由于socket设置了非阻塞,这里也要捕获异常
                        chunk = item.sk.recv(8096)
                        if not chunk:
                            break
                        chunk_list.append(chunk)
                    except BlockingIOError as e:
                        pass
                body = b''.join(chunk_list)  # 获取到结果
                item.func(body)              # 执行回调函数,完成异步
                self.socket.remove(item)    # 接收数据操作已完成,从socket列表中删除
            if not self.socket:
                break

def func(body):  #回调函数
    print(body)


t = Nb('www.baidu.com', key_list)
for i in key_list:
    t.add('www.baidu.com', func)

t.run()

 

 

 

 

协程

posted on 2018-09-12 16:28  Louiszj  阅读(116)  评论(0编辑  收藏  举报

导航