博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

python day 20: 线程池与协程,多进程TCP服务器

Posted on 2019-11-01 21:50  bluestarpin  阅读(638)  评论(0编辑  收藏  举报

python day 20: 线程池与协程

2019/11/1


资料来自老男孩教育

2. 线程

线程适用于IO密集流操作,线程是最小的执行单元
线程之间数据是共享的,共用一块内存
import threading :导入线程模块
t = threading.Thread(target=func,args=())
t.start()
t.join([timeout]),此处join是wait的意思,即主线程等待子线程结束,最多等2秒
主进程是否等待子线程
t.setDaemon(True/False)
线程锁:使用RLOCK
mutex = threading.RLock()创建锁
mutex.acquire()获得锁
mutex.release()释放锁
Event事件
event_obj = threading.Event()创建事件对象
event_obj.wait()等待状态,flag为False则阻塞所有的线程
event_obj.clear()将flag设置为False
event_obj.set()将flal设置为True

3. 进程

创建进程
import multiprocessing
if name=="main":
p = multiprocessing.Process(target=func,args=(),)
p.deamon = True/False(主进程是否等待子进程结束)
p.start()
p.join([timeout])主进程等待子进程结束,最多等待多少秒
进程之间数据不共享,每个进程使用独立内存
进程间数据共享的实现方式Manage或Array
Array
数组必须一开始就定义好数组的长度
数组的元素必须是统一的数据类型
Manage()
manage = Manage()
manage.dict()无长度限制,比Array方便,进程间数据通信使用manage.dict是最优选择。
进程池Pool
pool = multiprocessing.Pool(5),创建一个最多支持5个进程的进程池。
pool.apply(func,(1,))申请一个进程执行某个函数。每个任务是排队执行。每个进程有join执行。
pool.apply_async(func=Foo,args=(1,),callback=Bar),申请一个进程去执行Foo方法,将Foo方法的返回值作为实参赋值给Bar函数。每个任务是并发执行。更多是使用apply_async。每个进程没有join执行. 进程的deamon=True。
pool.close()关闭进程池,不再接收新请求。或pool.terminate()
pool.join(),进程池中进程执行完毕后主进程再关闭。

4. 协程:gevent模块,又叫微线程

必须安装greenlet模块与gevent模块
gevent代表高性能,当发IO请求时,尤其是网络IO请求时,使用协程
gevent.sleep(0)
gevent.joinall()

from gevent import monkey; monkey.patch_all()
import requests
import gevent

def f(url):
    print("url: %s"%url)
    ret = requests.get(url)
    data = ret.text
    print(url,len(data))

gevent.joinall([
    gevent.spawn(f,"https://juejin.im/post/5aa7314e6fb9a028d936d2a4"),
    gevent.spawn(f,"https://www.cnblogs.com/lanxing0422/p/pythonday19.html"),
    gevent.spawn(f,"http://www.baidu.com"),
])



# # 用于IO密集流
# def foo():
#     print("foo")
#     # 执行gevent的sleep方法
#     gevent.sleep(0)
#     print("foo again")
#
# def bar():
#     print("bar")
#     gevent.sleep(0)
#     print("bar again")
#
# gevent.joinall([
#     gevent.spawn(foo),
#     gevent.spawn(bar),
# ])

5. 扩展

生产者消费者模型
队列:Queue
队列的特性:先进先出
q = queue.Queue(max)
q.put()
q.get()
q.get_nowait
q.put_nowait()
rabbit_m_queue开源的,特别牛逼的

6. 自定义线程池

python内部没有提供,需要自定义。
实际使用中,更多是基于线程池来使用,而不是创建一个单独的线程。
非常重要,以后会经常使用。

import threading
import queue
import time
import contextlib

Stopevent = object()


class ThreadPool(object):
    '''
    进程池
    '''

    def __init__(self, max_num):
        self.max_num = max_num
        self.q = queue.Queue()
        self.terminate_flag = False
        # 真实创建的线程列表
        self.generate_list = []
        # 空闲中的线程列表
        self.free_list = []

    def generate_thread(self):
        '''
        创建一个线程执行任务
        :return:
        '''
        t = threading.Thread(target=self.call)
        t.start()

    def run(self, func, args, callback=None):
        '''
        线程池执行一个任务
        :param func: 任务函数名
        :param args: 任务函数所需参数元组
        :param callback: 任务执行失败或成功后执行的回调函数
        :return: 如果线程池已经终止,则返回True否则None
        '''

        if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
            self.generate_thread()
        tuple_obj = (func, args, callback)
        self.q.put(tuple_obj)

    def call(self):
        '''
        循环获取任务并执行
        :return:
        '''
        # 获取当前线程并添加到列表中
        current_thread = threading.currentThread()
        self.generate_list.append(current_thread)

        # 从队列中取任务
        event = self.q.get()
        # 当该任务的类型不是Stopevent时,死循环
        while event != Stopevent:
            func, args, callback = event
            status = True
            try:
                ret = func(*args)
            except Exception as e:
                status = False
                ret = e

            # 当回调函数不为空时,就执行回调函数
            if callback:
                try:
                    callback(status, ret)
                except Exception as e:
                    pass


            # self.free_list.append(current_thread)
            # event = self.q.get()
            # self.free_list.remove(current_thread)
            with self.work_state(self.free_list,current_thread):
                if not self.terminate_flag:
                    event = self.q.get()
                else:
                    event = Stopevent

        else:
            self.generate_list.remove(current_thread)

    def close(self):
        num = len(self.generate_list)
        while num:
            self.q.put(Stopevent)
            num -= 1

    def terminate(self):
        self.terminate_flag = True
        # 终止线程,不清空队列
        # max = len(self.generate_list)
        # while max:
        #     self.q.put(Stopevent)
        #     max -= 1
        # self.q.empty()
        # 终止线程且清空队列
        while self.generate_list:
            self.q.put(Stopevent)
        self.q.empty()


    @contextlib.contextmanager
    def work_state(self,state_list,work_thread):
        '''用于记录线程池中正在等待的线程数'''
        state_list.append(work_thread)
        try:
            yield
        finally:
            state_list.remove(work_thread)

def foo(i):
    time.sleep(0.1)
    print("当前i是>>>",i)
    return i + 100


def call_back(status, ret):
    print("ret:>>>", ret)


pool = ThreadPool(10)

for i in range(50):
    '''
    将任务放进队列中:
        创建线程: 
            只有空闲线程列表为空且创建的线程数量小于最大线程数时才会创建线程。
        从队列中取到任务,线程执行任务
    '''
    pool.run(func=foo, args=(i,), callback=call_back)
pool.close()
print("gene_list",len(pool.generate_list))

7. 实现多进程TCP服务器

serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
重新设置套接字选项,重复使用绑定的信息
当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到SO_REUSEADDR选项。

from socket import *
from multiprocessing import *
# 处理客户端的请求并为其服务
def dealwithClient(newSocket):
    while True:
        recvData = newSocket.recv(1024)
        if len(recvData)==0:
            break
        else:
            print(recvData)
    newSocket.close()


def main():
    serSock = socket(AF_INET,SOCK_STREAM)
    # 设置套接字选项,使其可以重复使用绑定的信息
    serSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    bindAddr= ('',9091)
    serSock.bind(bindAddr)
    serSock.listen(5)

    try:
        while True:
            newSocket,clientAddr = serSock.accept()
            p1 = Process(target= dealwithClient,args=(newSocket,))
            p1.start()
            # 因为已经向⼦进程中copy了⼀份(引⽤) ,
            # 并且⽗进程中这个套接字也没有用处了
            # 所以关闭
            newSocket.close()
    finally:
        serSock.close()

if __name__=='__main__':
    main()

8. 实现多线程TCP服务器

from socket import *
from threading import *
# 处理客户端的请求并为其服务
def dealwithClient(newSocket):
    while True:
        recvData = newSocket.recv(1024)
        if len(recvData)==0:
            break
        else:
            print(recvData)
    newSocket.close()


def main():
    serSock = socket(AF_INET,SOCK_STREAM)
    # 设置套接字选项,使其可以重复使用绑定的信息
    serSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    bindAddr= ('',9091)
    serSock.bind(bindAddr)
    serSock.listen(5)

    try:
        while True:
            newSocket,clientAddr = serSock.accept()
            p1 = Thread(target= dealwithClient,args=(newSocket,))
            p1.start()
            # 因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可⽤,
            # 但是此时在线程中这个套接字可能还在收数据, 因此不能关闭
            # newSocket.close()
    finally:
        serSock.close()

if __name__=='__main__':
    main()

9. 协程greenlet和gevent

⽐线程更⼩的执⾏单元(微线程)
⼀个线程作为⼀个容器⾥⾯可以放置多个协程

只切换函数调用即可实现多任务,可以减少CPU的切换
协程⾃⼰主动让出CPU

使用生成器,只切换函数调用即可完成多任务切换

import time
def A():
    while True:
        print(“----A---”)
            yield  
        time.sleep(0.5)
def B(c):
    while True:
        print(“----B---”)
        c.next()
        time.sleep(0.5)
if __name__==‘__main__’:
    a = A() # 如果一个函数中有yield,返回值就是一个生成器
    B(a)
# python中的greenlet模块对协程进行了封装(底层相当于yield)
# 安装模块: pip3 install greenlet
from greenlet import greenlet
import time
def t1():
    while True:
        print("........A........")
        gr2.switch()
        time.sleep(1)
def t2():
    while True:
        print("........b........")
        gr1.switch()#调到上次执行的地方继续执行
        time.sleep(1)
gr1 = greenlet(t1)#创建一个greenlet对象
gr2 = greenlet(t2)
gr1.switch()#此时会执行1函数

python还有⼀个⽐greenlet更强⼤的并且能够⾃动切换任务的模块 gevent.
原理是当⼀个greenlet遇到IO(指的是input output 输⼊输出)操作时, ⽐如访问⽹络, 就⾃动切换到其他的greenlet, 等到IO操作完成, 再在适当的时候切换回来继续执⾏.
io密集型和cpu密集型:一些进程绝大多数时间在计算上,称为计算密集型(CPU密集型),此时用多进程.
有一些进程则在input 和output上花费了大多时间,称为I/O密集型。比如搜索引擎大多时间是在等待(耗时操作),相应这种就属于I/O密集型。此时用多线程.

import gevent
def A():
    while True:
        print(".........A.........")
        gevent.sleep(1)#用来模拟一个耗时操作
        #gevent中:当一个协程遇到耗时操作会自动交出控制权给其他协程
def B():
    while True:
        print(".........B.........")
        gevent.sleep(1)#每当遇到耗时操作,会自用转到其他协程
g1 = gevent.spawn(A) # 创建一个gevent对象(创建了一个协程),此时就已经开始执行A
g2 = gevent.spawn(B)
g1.join()  #等待协程执行结束
g2.join()  #会等待协程运行结束后再退出