• python_21_线程+进程+协程


    python_线程_进程_协程

    什么是线程?

           -- os能够进行运算调度的最小单位,被包含在进程之中,是一串指令的集合

           -- 每个线程都是独立的,可以访问同一进程下所有的资源

    什么是进程?

           -- 每个进程独立,对应的内存也独立,不可互相访问,为了安全

           -- 包含各种对资源的调用,各种资源的集合,以一个整体暴露给os管理

           -- 进程要操作cpu,必须先创建一个线程,

      -- 进程只是向操作系统要了资源,通过线程才能使用资源

      -- 一个进程至少有一个主线程,其主线程可以创建子线程,子线程又可以创建子线程

    进程快还是线程快?

      这个问题本身有问题!

    进程与线程区别?

      -- 线程共享内存空间,进程内存是独立的,

      -- 虽然是克隆相同的数据,但两个子进程数据都是两个独立的数据

      -- 同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现

      -- 创建新线程很简单,创建新进程需要对父进程进行一次克隆

           -- 一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程

           -- 修改主线程,可能会影响其他线程运行行为,修改父进程而并不是删除,子进程不会受到影响

    什么是并发?

      -- 假如阅读一本书,我只需要记住读到了第几页,第几行,第几个字符,

        也就是说只要记住3个数字,可以在休息完了之后,接着读,室友在我休息的时候,

        也可以按此方法阅读这本书,从而实现书的共享和利用率,把这本书同样看作cpu,

        依据上下文,就可以实现快速的切换线程,进程

      -- 单核cpu依据上下文,实现快速的切换,给了一个幻觉,可以同时执行多个任务,实际上,任何时候只能执行一个线程

      -- 对于多核心cpu实实在在同时可以执行多个任务

    什么是python GIL?

           -- 多核cpu可以同时执行多个线程

      -- 全局解释器锁

      -- 在python中无论启动多少个线程,无论cpu有多少核心,python在执行的时候,

        会淡定的在同一时刻只允许一个线程运行,。

      -- python线程调用os原生线程,c语言原生线程接口,必须把上下文传给os,对于调用接口,

        解释器无法对cpu处理顺序处理,解释器无法控制线程执行顺序,只能调用等结果,

        对于结果有可能对,有可能错,多个线程可以同时多核执行,但同时只有一个线程对结果进行修改,

        Cpython才有这个问题

    如何实现一个最简单的多线程?

     1 import threading                                   #导入线程模块
     2 import time
     3 def run(n):                                        # run可以随便改名
     4     print("talk", n)
     5     time.sleep(2)
     6 
     7 t_1 = threading.Thread(target=run, args =('t1',))   # 实例一个线程
     8             # target =目标函数 ,args =参数(元组形式,一个参数也要加逗号)
     9 t_2 = threading.Thread(target=run,args=('t2',))
    10 
    11 t_1.start()             # 启动线程                  
    12 t_2.start()

           还有个方式,通过类来写

     1 import threading
     2 import time
     3 class MyThread(threading.Thread):                     # 继承threading.Thread
     4     def __init__(self,name):                          # 重构父类析构函数
     5         super(MyThread,self).__init__()               # 继承父类析构函数
     6         self.name = name
     7     def run(self):                                    # 必须写run
     8         print('talk',self.name)
     9         time.sleep(2)
    10         
    11 if __name__ == '__main__':
    12     t_1 = MyThread('t1')                               # 实例一个线程
    13     t_2 = MyThread('t2')
    14     
    15     t_1.start()
    16     t_2.start()

    -- 对于主线程和子线程来说,程序本身就是个主线程,主线程执行时候并不等待子线程,对于整个程序主线程结束时候,会等待子线程全部结束

    如何实现简单计算线程运行时间?

     1 import threading
     2 import time
     3 
     4 class MyThread(threading.Thread):
     5     def __init__(self,name):
     6         super(MyThread,self).__init__()
     7         self.name = name
     8     def run(self):
     9         print('talk',self.name)
    10         time.sleep(2)
    11         print('%s is done'%self.name)
    12         
    13 start_time = time.time()
    14 if __name__ == '__main__':
    15     list_t = []                 # 通过列表关联有多少个线程,循环此列表进行线程等待
    16     for i in range(50):
    17         t = MyThread(i)         # 实例一个线程
    18         # t.setDaemon(True)     # 此为守护线程声明,不重要,必须在start前面加
    19         t.start()  
    20         list_t.append(t)        # 每实例一个线程都加入列表,通过列表长度判断添加了几个进程
    21 for i in list_t:               
    22     t.join()                    # 循环列表,给每个线程加上join()等待,当子线程成为守护线程时候,不需要join
    23 end_time = time.time()
    24 
    25 print('running time is : %s'%(end_time - start_time))
    26 
    27 print('The mian process.....')

      通过 ”线程名.join()” 来进行等待子线程结束,主线程才继续执行

      对于多线程同时共享一份数据,更改同一份数据,有哪些坑?

        -- GIL 锁 + - 数据进行修改多了,修改不准,大坑,GIl 锁只能保证同一时间只有一个线程在执行,

          可以使用thread lock保证同一时间只有一个进程进行数据修改,在修改的时候,保证线程串行,

          在2.x版本有问题,在3.x此问题解决了,但还是要加上锁,官方没有声明

           在2.x上如何解决?

             lock = threading.Lock()      #实例化锁

             def run(self):

                lock.acquire                #加锁,其他线程进入等待状态

                global num

                num += 1

                lock.release()              #释放锁

           -- 但是此锁在有嵌套锁的时候,就会出现锁住无法释放锁的时候,就得使用递归锁

                  lock = threading.RLock()   --R表示递归锁

          -- 互斥锁,同时只允许一个线程修改数据。

    什么是守护线程?

           -- 主线程执行完,退出,不管守护线程是否执行完毕,程序全部退出,主人+奴隶

           -- 启动线程之前,加上 “线程对象.setDaemon(True)”

    如何查看主线程和活跃的线程计数?

           threading.current_thread()               # 查看主线程

           threading,active_count()                  #查看活跃线程格式

    什么是信号量?

      -- 信号量和锁一样,拥有多把锁,分配锁,让有锁的线程修改数据,

        也说明同时让有锁的线程并行,没有锁的等待,完成的线程释放锁,

        等待线程依次获得锁,但是锁数量是有限制的

             先实例一个semphore对象 “semphore = threading.BoundedSemaphoer(5)”

      # 同时运行5个获得锁的线程

         def run(self):

              semphore.acquire                           #加锁,其他线程进入等待状态

              global num

              num += 1

             semphore.release()                           #释放锁

    什么是事件?

      -- Events 进行不同线程之间同步数据

      (红绿灯,每隔多少秒变灯,车子不停检查灯的状态,红灯停,绿灯行,

        本质上声明全局变量,红绿灯满足条件更改全局变量,车子不停检查全局变量)

      event = threading,Event()

      event.set()                                # 设置标志位

      event.clear()                             # 清空标准位

      event.is_set()                            # 判断标志位是否设置

      event.wait()                               # 标志位被清空一直堵塞状态,直到标志位再次设定不堵塞状态

           如何实现一个红绿灯程序?

     1 import threading
     2 import time
     3 
     4 event = threading.Event()               # 声明事件
     5 
     6 def light():                            # 定义红灯
     7     event.set()                         # 设置事件
     8     conut = 0                           # 计时
     9     while True:
    10         if conut > 5 and conut < 10:    # 在5到10秒内,红灯,清空事件
    11             event.clear()
    12             print('light red........')
    13         elif conut > 10 :               # 绿灯,设置事件
    14             event.set()
    15             print('light green.......')
    16             conut = 0
    17         else:
    18             print('light green.......')
    19         time.sleep(1)
    20         conut += 1
    21 
    22 def car(name):                          # 定义车
    23     while True:
    24         if event.is_set():              # 检查事件状态,如果是红灯就停止
    25             print('[%s] is run......'%name)
    26             time.sleep(1)
    27         else:
    28             print('[%s] is wait......'%name)
    29             event.wait()                # 被清空处于堵塞状态
    30             print('[%s] is run......' % name)
    31 
    32 if __name__ == '__main__':
    33     light_1 = threading.Thread(target=light)    # 实例红灯线程
    34     light_1.start()                             # 启动
    35     car_1 = threading.Thread(target=car,args=('哈士奇',))  # 实例车进程
    36     car_1.start()                                          # 启动

     

    什么是队列?

           -- 一种数据类型容器,先进先出

    列表和队列的区别?

           -- 列表取出一个数据,列表中那个数据还在

      -- 但对于队列数据,取走一个少一个

    为什么需要队列?

      -- 提高排队者和处理者的效率

      --比如50个人拷贝一份资料,一份的时间5分钟,把U盘留下,按顺序来,排队者可以不用排起队,

        干等着,可以在这段时间去干其他事,计算号时间然后去拿就行了,对于处理者来说,

        1个人处理要250分钟,加1个人帮忙,只要125分钟可以完成,对于处理者来说我怎么处理,

        排队的人不需要关心,处理者只需要把资料拷贝好了交给排队者就行了

    1. 解耦
    2. 提高效率

    如何生成使用一个队列?

           queue 模块,有三种队列,先进先出+后进先出+设置优先级队列

           q = queue.Queue(block=True,maxsize =数字)   --实例一个q对象,先进先出

                  -- block 默认等于True,取数据没有的时候一直处于堵塞状态,等待放入数据

                  -- maxsize 默认不写,任意put数据,写的,多了处于堵塞状态,需等待数据取出再放入

           q = queue.LifoQuenue(maxsize=0)        -- 后进先出

           q = queue,PriorityQueue(maxsize=0)     -- 设置优先级

                  -- q.put((优先级,数据))                   -- 数字越小优先级高

           q.put(block=True,timeout=None)          -- 放入一个元素

           q.qsize()                                                 -- 查看长度

           q.get(block=True,timeout=None)                       

        -- 取一个数据,当没数据时候,一直等待放入数据然后取状态,堵塞状态

           q.get_nowait()            -- 取一个数据,当没数据时候,抛出异常

           q.get(timeout = 1)       -- 取数据1秒钟,1秒钟之后没有数据抛出异常

    什么是生产与消费模型?

           -- 用于解决大多数并发问题,平衡生成能力和消费能力来提高整体处理数据能力

      -- 生产者和消费者之间不彼此通信,对于生产者只需要把生产的商品放入队列,

        队列满了,停止生产,等待消费者从队列中消费商品。对于消费者,

        只需要到队列中拿商品,队列商品没了,进入等待状态,等队列中出现新的商品。

      -- 供求关系

    如何实现?

     1 import threading
     2 import time
     3 import queue
     4 
     5 q = queue.Queue(maxsize=10)
     6 
     7 def p(name):
     8     count = 1
     9     while True:
    10        print('%s生产第%s包子'%(name,count))
    11        q.put('包子%s'%count)
    12        count += 1
    13        time.sleep(1)
    14 
    15 def s(name):
    16     while True:
    17          q.get()
    18          print('%s作死的吃,如狼似虎'%name)
    19          time.sleep(1)
    20 
    21 if __name__ == '__main__':
    22     p1 = threading.Thread(target=p, args=('一枝花',))
    23     s1 = threading.Thread(target=s, args=('北门吹雪',))
    24     s2 = threading.Thread(target=s, args=('西门吹风',))
    25 
    26     p1.start()
    27     s1.start()
    28     s2.start()

      -- I/O操作不占用cpu,读取输入输出

      -- 计算占用cpu,数学运算不适合多线程

      -- python多线程,不适合cpu密集操作型的认为,适合I/O密集型的任务,(socketserver)

      -- python中启多个进程,每个进程都有一个主线程,运用多进程来实现多核心cpu同时运行的问题

      -- 在linux中每个子进程都是由父进程启动的,必有一个根进程

    如何启动多进程?

           imoprt multprocessing              #导入多进程模块

           p = multiprocessing.Process(target = 方法,args = 元组)

           p.start()

                                              # 多进程的语法规则和多线程执行方法类似,进程中可以嵌套启动线程

           os.getppid()                              #获得当前父进程id

           os.getpid()                                #获得当前程序子进程id

      如何让两个进程之间进行通信?

           queue                                       # 线程 队列 ,线程之间共享数据

           Queue                                      # 进程 队列 ,进程之间共享数据

      进程之间数据和内存地址是独立的,无法互相访问各自的内存地址

        -- 如果把线程q当做一个一个参数传给子进程,相当于序列化传给子进程

        -- 对于进程Q,把Q对象当做参数传入子进程,相当于克隆了一份一样的,本质上是pikle

        一份传给个子进程,本质上虚拟出一个中间Q,子进程load一下,父进程dump一下

      实现代码:

     1 from multiprocessing import Process,Queue   # 导入
     2 
     3 def child_1(Q):
     4     Q.put('hello')
     5     
     6 if __name__ == '__main__':
     7     q = Queue()             
     8     p = Process(target=child_1, args=(q,))
     9     p.start()
    10     p.join()
    11     print(q.get())

      实现了一个进程放数据,另外一个进程拿到数据

      还有个pipes实现进程之间通信?

             --     相当于建立起电话连接,类似socket,发一条收一条,多收了就会进入等待状态

    from multiprocessing import Process,Pipe
    
    def child_1(Q):
        print('来自一枝花的祝福:',Q.recv())
        Q.send('兄弟,好久没见面了')
    
    if __name__ == '__main__':
        brother_1, brother_2 = Pipe()
        p = Process(target=child_1, args=(brother_2,))
        p.start()
        brother_1.send('北门吹雪可好?')
        print("来自北门吹雪的回话:",brother_1.recv())
        p.join()

      上面只是完成数据的传递,而不是共享,如何解决?

    进程之间可以通过Manager进行共享数据?

           -- 本质上和Queue一样

     1 from multiprocessing import Process,Manager
     2 import os
     3 
     4 def show(d,l):
     5            d[os.getpid()] = os.getpid()          #每个进程都修改共享字典
     6            l.append(os.getpid())                 #每个进程都修改共享列表
     7 if __name__ == '__main__':
     8     with Manager() as manager:                   # 等价于manager = Manager()
     9         d = manager.dict()                       # 生成一个共享字典
    10         l = manager.list()                       # 生成一个共享列表
    11         p_list = []                              # 确定每个进程都完成的列表
    12         for i in range(10):
    13             p = Process(target=show, args=(d,l))      #把共享列表和字典当做参数传入进程
    14             p.start()
    15             p_list.append(p)
    16         for p in p_list:                               #确定每个进程都完成
    17             p.join()
    18         print(d,l)
    19        # 对于此处,修改数据不需要加锁,Manager自动加上锁了

    如何逻辑整理?

           -- Queue、Pipe、Manager、Process模块都是multiprocessing模块中自带

      -- Queue、Manager实现通信的本质上是一样的,

      - 但是Queue只能实现简单的put和get,

      Manager不仅可以实现共享字典和列表,还可以进行任何对共享字典和列表的操作

        -- Pipe的使用,有点类似socket,开始实例两个通信对象,然后把其中一个对象当做参数,传入另外一个进程,同时send,recv实现进程之间通信

        -- Queue、Pipe、Manager实现通信的逻辑,都是先生成通信对象的中间桥梁,通过线程实例化的时候,把通信对象当做参数传入进去,从而实现通信

             -- 现实生活中,要想打电话的前提,必须要有手机,手机就是其中沟通的桥梁

    什么是进程锁?

        -- 锁住对屏幕资源的抢占,和线程锁一毛一样,先从multiprocessing中导入Lock,

        然后生成锁的实例,然后把锁当做参数传入进程实例,通过 .acquire() 锁上屏幕输出,.release()释放锁

    什么是进程池?

           -- 同一时间规定多少个进程在cpu上运行

           如何实现?

    1. 导入pool,在multiprocessing中
    2. 实例pool,process = 进程池进程数量
    3. 实例进程,”.apply”表示同步(串行),”.apply_async”表示异步(并行)

      func = 进程函数,args =(进程参数,),callback = 回调函数(异步才有)

        回调函数是每个子进程执行完毕,再调用此函数,是主线程进程调用的,提高运行效

      先pool.close(),再pool.join(),大坑,逻辑上是先join再close,但是此处不符合逻辑

    逻辑代码

     1 from multiprocessing import Pool
     2 import os
     3 import time
     4 
     5 def show(q):                                     # 进程函数
     6     time.sleep(2)
     7     print(os.getpid())
     8    
     9 def callback_1(a):                            # 回调函数,主进程调用
    10     print('回调函数',os.getpid())
    11           
    12 if __name__ == '__main__':
    13     with Pool(5) as pool:               # 线程池只有5个进程,相当于pool = Pool(5)
    14         for i in range(10):                      #启动10个进程
    15             pool.apply_async(func=show,args=(i,),callback=callback_1)
    16             pool.close()                               # 坑,先close再join,逻辑坑
    17             pool.join()

    什么是协程?

           -- 微线程,一种用户态的轻量级线程

           -- 单线程实现并发,本质上在不同函数中来回切换,串行,通过yield

           优点:

                  - 无需线程上下文切换

                  - 无需原子操作锁定及同步的开销

                  - 方便控制流,简化编程模型

                  - 高并发+高扩展+低成本,一个cpu可以运行上万个协程

           缺点:

                  - 无法利用多核心cpu,需要和进程进行配合使用

    协程的逻辑?

       -- 通过yield,让函数变成生成器,先通过实例一个迭代对象,

        然后通过迭代对象.__next__()方法进行函数一次调用,遇到yield函数跳出当前函数执行,

        去执行其他逻辑函数,其他函数中通过迭代对象.send(参数)方法,参数被迭代器函数yield接收到,

        唤醒迭代对象函数,执行yield函数下的函数体,再次遇到yield,再次跳出来,

        回到 “对象.send(参数)”后面的逻辑函数,一遇到对象.send(参数),

        又回到对象函数中yield函数后面的逻辑函数执行,如此反复,在程序中进行跳转,给了一个多并发的幻觉

    -- 实现模型是生产者消费模型?

     1 import time
     2 
     3 def producer(name):                        # 定义一个生产者
     4     c_1.__next__()                         # 生成器调用
     5     c_2.__next__()
     6     for i in range(2,100,2):
     7         print('%s生产了[%s]包子'%(name,i))
     8         c_1.send(i)
     9                 # 跳出此函数,唤醒c_1迭代器中yield后面的逻辑函数并且执行,一遇到yield或者
    10                 # c_1迭代对象函数已经执行完了,又跳回到此处,继续执行
    11         c_2.send(i)
    12 def consumer(name):
    13     print('开始要吃包子了')
    14     while True:
    15         baozi = yield
    16         print('%s正在吃包子%s'%(name,baozi))
    17         time.sleep(1)
    18 
    19 if __name__ == '__main__':
    20     c_1 = consumer('一枝花')
    21     c_2 = consumer('北门吹雪')
    22     p = producer('酒酒')

        -- 上面有个问题,生产者无任何堵塞,幻觉上实现了并发,但是一旦生成者有堵塞,就会有卡顿,如何解决?

    协程如何实现的?

      -- 一遇到io请求,协程把io请求和回调函数交个os,os处理完了调用回调函数,通知我,然后又跳回来执行剩下的逻辑处理,如此反复

    协程切换原则?

           -- 遇到I/O操作就切换,那什么时候切回去?I/O操作完成切换回去

           -- 通过 greenlet手动的切换协程

           代码实现:

     1 from greenlet import greenlet   # 导入协程模块
     2 
     3 def f1():                       # 定义一个协程函数1
     4     print(1)                    # 协程1切换执行的函数
     5     g_2.switch()                # 切换到协程函数2,也是协程函数1的回来执行得起点
     6     print(2)                    # 协程1切换回来执行函数
     7     g_2.switch()                # 切换到协程2函数
     8 
     9 def f2():                       # 定义协程2函数
    10     print(3)                    # 协程2执行的函数
    11     g_1.switch()                # 切换到协程函数1,也是协程函数2的回来执行得起点
    12     print(4)                    # 协程2执行的函数
    13 
    14 g_1 = greenlet(f1)              # 启动协程1
    15 g_2 = greenlet(f2)              # 启动协程2,启动协程并不代表,执行协程函数
    16 
    17 g_1.switch()                    # 执行协程1

        - 以上函数虽然实现了协程,但是手动执行,没有自动化

    如何实现自动挡协程?

     1 import gevent                              # 引入协程模块
     2 
     3 def f1():                                  # 定义协程函数
     4     print('hello 1')                       # cpu处理
     5     gevent.sleep(3)                        # 模拟i/o操作,跳出
     6     print('hellw 1 again')   
     7 
     8 def f2():
     9     print('hello 2')
    10     gevent.sleep(2)
    11     print('hello 2 again')
    12 
    13 def f3():
    14     print('hello 3')
    15     gevent.sleep(4)
    16     print('hello 3 again')
    17 
    18 gevent.joinall([
    19     gevent.spawn(f1),                       # 启动协程
    20     gevent.spawn(f2),
    21     gevent.spawn(f3),
    22 
    23 ])

        -- 上面代码牛逼之处在于,代码的中运行时间与最大运算时间有关,

        上面代码最长运行时间4秒,一遇到io操作就切换,依次从上往下跳,

        自动检查I/O操作是否完成,实际上I/O操作和程序执行进行了异步,分开了

    如何使用协程进行大并发爬网页?

           -- urlib 和socket,gevent无法直接知道其中I/O操作

           如何实现?

     1 import requests
     2 import gevent
     3 from gevent import monkey
     4 
     5 monkey.patch_all()  # 把当前程序的所有的io操作给单独做上标记,打上补丁
     6 
     7 def pa(url):
     8     try:
     9         r = requests.get(url, timeout=30)
    10         r.raise_for_status()
    11         r.encoding = r.apparent_encoding
    12         data = r.text
    13         print(url, data)
    14     except:
    15         print(url, '爬取错误')
    16 
    17 gevent.joinall([
    18     gevent.spawn(pa, 'http://www.ithome.com'),
    19     gevent.spawn(pa, 'http://www.baidu.com'),
    20     gevent.spawn(pa, 'http://lol.qq.com')
    21 ])

    如何实现高效socketserver?高并发?

           服务端:

     1 import socket                         # 导入socket
     2 import gevent                         # 导入协程模块
     3 from gevent import monkey             # 导入协程标记模块
     4 
     5 monkey.patch_all()                    # 启动协程标记
     6 
     7 def server(port):                     # 定义socket服务
     8     s = socket.socket()               # 实例socket服务
     9     s.bind(('localhost',port))        # 绑定端口
    10     s.listen(5)                       # 启动监听
    11     while True:                       # 提供服务
    12         conn, addr = s.accept()       # 监听对象和对象地址
    13         gevent.spawn(handle, conn)    # 启动处理请求协程
    14 
    15 def handle(Q):                        # 定义处理函数
    16     while True:                       # 循环接收客户端请求
    17         try:
    18             data = Q.recv(1024)                 # 接收数据
    19             print(data.decode('utf-8'))         # 打印接收数据
    20             Q.send(data.upper())                # 发送处理数据
    21         except Exception as e:                  # 异常打印
    22             print(e)                            # 打印异常
    23             break                               # 异常跳出程序
    24 
    25 if __name__ == '__main__':
    26     server(6969)                                # 输入端口,启动服务

    什么是用户空间和内核空间?

           os采用虚拟存储器,对32os,寻址空间为2的32次方

      os的核心是内核,独立于普通的应用程序,可以访问保护的内存空间,也有访问底层硬件设备的所有权限

      为了保护内核的安全,用户进程不能直接操作内核,os把虚拟空间划分为两个部分,一个是内核空间,另外一个是用户空间

    什么是进程切换?

        内核有能力挂起正在运行的进程,并恢复以前挂起的某个进程,

        进程的堵塞状态是进程本身主动请求的,通过进程的上下文,快速的在cpu中运行与堵塞

    什么是进程堵塞?

        进程请求资源失败、等待某种操作完成、无工作可做等状态,则有os自动执行堵塞原语(block),

        让自由运行的进程,进入堵塞状态,获得cpu资源的进程才有堵塞状态,

        堵塞状态不占用cpu资源,堵塞状态是进程的一种主动请求

    什么是文件描叙符?

      -- 形式上是一个非负整数,实际上它是一个索引值,指向内核为每一个进程所维护的改进程打开文件的记录表

      -- 当程序打开一个现有的文件或者创建一个新文件的时候,内核向进程返回一个文件描叙符

    什么是缓存IO?

           数据先被拷贝到内核缓存区中,然后才会从os内核中拷贝到应用程序地址空间

           -- 数据 ——》内核空间——》用户空间

           -- 数据的反复拷贝,cpu和内存资源开销非常大

    什么是IO模式?

    1. 等待内核数据准备
    2. 将数据从内核拷贝到进程中

    linux操作系统产生哪些网络模式?

    1. 堵塞IO                 – 等数据,拷贝数据
    2. 非堵塞IO              -- 没数据返回erro,一旦有数据并且收到请求,拷贝数据,需要一直检测
    3. IO多路复用           -- select,poll,epoll
    4. 信号驱动IO
    5. 异步IO                  -- python模块 asyncio

    什么是异步IO?

           不会导致请求IO用户的堵塞。用户请求,滚,把数据送到用户家门口

      任何堵塞是服务器堵塞,任何模式都是改变用户等待请求的方式

    什么是IO多路复用?

      -- select,poll,epoll区别?

           select:一种循环检测请求数据,数据来了,内核告诉用户数据来了,保密,需要用户循环接收

           poll:

      epoll: 一种循环检测请求数据,数据来了,告诉用户数据来了,哪个活跃,用户拿着这两个数据去取数据

      不取数据,数据本身还在内核空间中,内核每次都通知用户去取       -- 水平触发

      不取数据,数据本身还在内核空间中,内核不再通知用户去取           -- 边缘触发

    如何使用select建立一种socket模型,实现并发?

    1. 导入select,socket模块
    2. IO设置为非堵塞(server.setblocking(Flase))
    3. 设置select检查对象,并且让selet循环
    4. 每进来一个新的连接,加入到select检查列表中,如果连接发数据,活动起来,设置一个队列进行put客户端发来的数据
    5. 通过下次循环开始,再把处理数据发送给客户端,虽然慢了一步,但是实现了封装
    6. 通过select返回的erro列表,如果连接断开,删除检查列表中连接对象,删除队列中连接对象数据
     1 #!/usr/bin/python3
     2 import socket
     3 import select
     4 import queue
     5 
     6 server = socket.socket()
     7 server.bind(('localhost',6969))
     8 server.listen(5)                            # 声明协议,启动监听
     9 server.setblocking(False)                   #设置为非堵塞
    10 
    11 inputs = [server,]                          #生成检测列表
    12 #inputs = [server,conn,conn2]
    13 outputs = []                                # 下次循环开始就返回上次循环加入的数据
    14 client_data = {}                            # 定义一个接收各个连接客户端发来数据的字典
    15 
    16 while True:                                 # 让select一直检测
    17     readable, writeable, exceptional = select.select(inputs,outputs,inputs)
    18     print(exceptional)               # readable, writeable, exceptional,三个对象是固定参数,不可更改
    19     for r in readable:        # readable中返回的是活动的连接列表,不活动不返回,r是活着的连接
    20         if r is server:                     # 判断是否有新的连接
    21             conn, addr = server.accept()    # 接收数据,非堵塞
    22             inputs.append(conn)             # 把新的连接加入到select中检测
    23             client_data[conn] = queue.Queue()       # 一个新的连接生成一个队列对象
    24         else:
    25             try:
    26                 data = r.recv(1024)               # 接收数据
    27                 client_data[r].put(data)          # 把数据放入字典中的队列中
    28                 outputs.append(r)             # 向outputs 中加入连接对象,下次循环返回给writeable
    29             except Exception as e:
    30                 print(r)
    31                 inputs.remove(r)              # 远程客户端断开连接,移除检查对象
    32                 
    33     for w in writeable:                             # 循环把客户端的发来的请求处理发回给客户端
    34         send_data = client_data[w].get()            # 获取队列中请求
    35         w.send(send_data.upper())                   # 发送处理的数据
    36         outputs.remove(w)                           # 发完了,移除output中连接对象
    37                                                     # 实际上更加慢了,装逼,包装
    38     for e in exceptional:                       # 断开连接
    39         if e in outputs:                        # 判断是否连接对象在outputs中
    40             outputs.remove(e)                   # 删除outputs连接对象
    41         inputs.remove(e)                        # 建立连接就会在inputs中
    42         del client_data[e]                      # 删除连接对象的字典中队列数据

     

    什么是selectors模块?

           -- 封装了select和epoll,默认优先级epoll先,不支持epoll才切换成select   

     1 import selectors
     2 import socket
     3 
     4 sel = selectors.DefaultSelector()
     5 
     6 def accept(sock, mask):
     7     conn, addr = sock.accept()  # Should be ready
     8     print('accepted', conn, 'from', addr)
     9     conn.setblocking(False)
    10     sel.register(conn, selectors.EVENT_READ, read)
    11 
    12 def read(conn, mask):
    13     data = conn.recv(1000)  # Should be ready
    14     if data:
    15         print('echoing', repr(data), 'to', conn)
    16         conn.send(data)  # Hope it won't block
    17     else:
    18         print('closing', conn)
    19         sel.unregister(conn)
    20         conn.close()
    21 
    22 sock = socket.socket()
    23 sock.bind(('localhost', 10000))
    24 sock.listen(100)
    25 sock.setblocking(False)
    26 
    27 sel.register(sock, selectors.EVENT_READ, accept)
    28 
    29 while True:
    30     events = sel.select()
    31     for key, mask in events:
    32         callback = key.data
    33         callback(key.fileobj, mask)

     

  • 相关阅读:
    记录一下守护进程的管理命令
    发布自己的第一版asp.net core的RESTful接口程序
    自己写一个chrome扩展程序
    DynamicJson-好用dotnet4的json对象
    Java提高——多线程(三)同步、锁
    Java提高——多线程(二)join、sleep、yield
    Java提高——多线程(一)状态图
    Git小结
    一个扎心的错——Consider defining a bean of type 'java.lang.String' in your configuration.
    关于mybatis对实体类参数绑定参数的问题
  • 原文地址:https://www.cnblogs.com/2bjiujiu/p/6668932.html
Copyright © 2020-2023  润新知