20.selector/线程回顾

selectors 模块

import selectors
# 基于select模块实现的IO多路复用,建议大家使用
import socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8080))

sock.listen(5)
sock.setblocking(False)

sel = selectors.DefaultSelector()
# 根据具体平台选择最佳IO多路机制,比如在linux上会优先选择epoll
# print(sel)


def read(conn, mask):
    try:
        data = conn.recv(1024)
        print(data.decode('utf-8'))
        conn.send(data.upper())
    except Exception:
        sel.unregister(conn)


def accept(sock, mask):
    conn, addr = sock.accept()
    print(conn)
    sel.register(conn, selectors.EVENT_READ, read)


# 注册,传入socket对象,第三个参数为运行的函数
sel.register(sock, selectors.EVENT_READ, accept)
# 将sock和accept绑定到sel当中去


while 1:
    # 监听
    print('waiting data….')
    event = sel.select()  # 获取活动对象[(key,mask)]

    for key, mask in event:
        # print(key.data)  # accept 函数
        # print(key.fileobj)  # 活动的soc对象

        func = key.data
        obj = key.fileobj

        func(obj, mask)

IO多路服用实现机制

windows下 select
linux 下除了select之外还有poll 和epoll
select缺点

  1. 每次调用select都需要把所有的文件描述符(fd)拷贝到内核空间,导致效率下降
  2. 遍历所有的fd,是否有数据访问
  3. 最大连接数是有阈值的

poll:
最大连接数,没有限制

epoll:通过三个函数来实现
第一个函数:创建epoll句柄:将所有的fd拷贝到内核空间,但是只需要拷贝一次;
第二个函数:回调函数:某一个事件(函数/动作)结束的时候,会自动调用的函数。为所有的fd绑定一个回调函数,一旦有数据访问,触发该回调函数,回调函数将fd放到链表中;

队列(queue)

队列在多线程编程,多个线程安全交换数据的情况下尤其有用。

import queue
q = queue.Queue()   #可以传入参数最大传入值
q.put(111)
q.put('hello')
q.put('super')
q.put('test',False)  #如果取值只有三个,第四个数据进入的时候也会默认blocking,这时候添加False就会直接报错,而不是阻塞在这里
ret1 = q.get()
ret2 = q.get()
ret3 = q.get()

ret4 = q.get(True)    #取值取完之后仍然想要取值,这时候会默认blocking(阻塞),在get中传入True后就会不阻塞,直接返回错误值empty

优点:线程安全

join和task_done方法

q = queue.Queue(5)
q.put(111)
q.put(333)

a = q.get()
b = q.get()
print(a,b)
q.task_done()
q.task_done()
#有几个操作就要有几个task_done,虽然这里只有一个print,但是task_done和print这种操作无关,只和队列相关的操作相关!
q.join()
print('over')

先进后出

queue.LifoQueue()    # lifo    last in first out
q.put(11)
q.put(22)
q.put(333)
print(q.get())

## 优先级
q = queue.PriortyQueue()
q.put([1,'hello1'])
q.put([2,'hello2'])
q.put([3,'hello3'])
q.put([4,'hello5'])
q.put([5,'hello4'])

while not q.empty:
    q.get()

生产者消费者模型

回顾

线程与进程

为什么需要: 实现并发(切换----由操作系统实现)

进程:是最小的资源管理单位(可以理解为一个存放线程的容器,一个py程序里,最少有一个线程,就是主线程)
线程:是最小的执行单位

并发:同一时间只有一个线程在执行,但是分布式执行线程,可以让某个线程执行一段时间后切换给另外一个线程执行,交替进行。但是由于需要保存各个线程的执行状态,会有消耗
并行:多个任务同一时刻都在执行,调用了多核cpu
串行:同一时间只有一个线程在执行,执行完才进行下一个线程

多进程和多线程都可以执行多个任务,线程是最小的执行单位进程是最小的资源单位,线程跑在进程中,一个进程中一定有一个线程。
线程的特点是线程之间可以共享内存和变量,资源消耗少(在Unix环境中,多进程和多线程资源调度小号差距不明显,Unix调度较快),缺点是线程之间的同步和枷锁比较麻烦。

GIL锁:

cpython的解释器因为存在GIL,导致:同一时刻,同一进程,只能有一个线程被执行

Tread

如何创建

jion方法和setDaemon方法

daemon:程序直到不存在非守护线程时退出
是不是可以理解为,当程序只剩下主线程和守护线程的时候就退出

同步锁

由于多线程处理公共数据
存在一个全局变量,所有的线程都可以调用操作这个变量。由于GIL锁,同一时间同一进程只有一个线程在执行。通过sleep模拟一个IO操作,由于线程1在执行IO操作,这时候切换到线程2,线程1的状态被保存。此时由于线程1在执行操作前出现IO操作,并没有来得及对数据进行操作,所以线程2获得的数据仍然是原来的全局变量。

递归锁和死锁

posted @ 2017-09-06 22:17  sc0T7_ly  阅读(142)  评论(0编辑  收藏  举报