• 进程、线程、协程


    进程

    1、什么是进程(process)?

      定义:1)进程是资源分配最小单位

         2)当一个可执行程序被系统执行(分配内存资源)就变成了一个进程

          1. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程

          2. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念

          3. 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。 

          4. 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

          5. 进程之间有自己独立的内存,各进程之间不能相互访问

          6. 创建一个新线程很简单,创建新进程需要对父进程进行复制

          多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行

          单道编程: 计算机内存中只允许一个的程序运行

          进程并发性:

              1)在一个系统中,同时会存在多个进程被加载到内存中,同处于开始到结束之间的状态

              2)对于一个单CPU系统来说,程序同时处于运行状态只是一种宏观上的概念
                   他们虽然都已经开始运行,但就微观而言,任意时刻,CPU上运行的程序只有一个

              3)由于操作系统分时,让每个进程都觉得自己独占CPU等资源

              注:如果是多核CPU(处理器)实际上是可以实现正在意义的同一时间点有多个线程同时运行

          线程并发性:

              1)操作系统将时间划分为很多时间段,尽可能的均匀分配给每一个线程。

              2)获取到时间片的线程被CPU执行,其他则一直在等待,所以微观上是走走停停,宏观上都在运行。

              多核CPU情况:          

              如果你的程序的线程数少于CPU的核心数,且系统此时没有其他进程同时运行,那么这个程序的每个线程会享有一个CPU,

              当同时运行的线程数多于CPU核心数时,CPU会采用一定的调度算法每隔一段时间就将这些线程调入或调出CPU
              以确保每个线程都能分享一部分CPU时间,实现多线程并发。

    2、有了进程为什么还要线程?

        1. 进程优点:

                              提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率

        2. 进程的两个重要缺点

                              a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

                              b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。

                              c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻

             即能监听键盘输入、又能监听其它人给你发的消息

                              d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀

                              e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀

    线程

    1、什么是线程(thread)(线程是操作系统最小的调度单位)

      定义:1)线程是操作系统调度的最小单位

         2)它被包含在进程之中,是进程中的实际运作单位

         3)进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

      

        1. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

        2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

        3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

        4. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

        5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同

        6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程

        7. 两个进程想通信,必须要通过一个中间代理

        8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存

    2、进程和线程的区别

        启动一个线程比启动一个进程快,运行速度没有可比性。

        先有一个进程然后才能有线程。

        1、进程包含线程

        2、线程共享内存空间

        3、进程内存是独立的(不可互相访问)

        4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)

        5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现

        6、创建新线程很简单,创建新进程需要对其父进程进行克隆。

        7、一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。

        8、父进程可以修改不影响子进程,但不能修改。

        9、线程可以帮助应用程序同时做几件事

    3、进程和程序的区别

        1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体

        2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资 

         源或事件而被处于等待状态,因完成任务而被撤消

        3. 进程是系统进行资源分配和调度的一个独立单位

        4.一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)

        5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程

    多线程

      Python多线程编程中常用方法:

        1、join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,

         那么在调用线程的时就可以使用被调线程的join方法join([timeout]) timeout:可选参数,线程运行的最长时间

        2、isAlive()方法:查看线程是否还在运行
        3、getName()方法:获得线程名
        4、setDaemon()方法:主线程退出时,需要子线程随主线程退出,则设置子线程的setDaemon()

      1、线程2种调用方式:直接调用, 继承式调用

        

    import threading
    import time
    
    def sayhi(num):                                   # 定义每个线程要运行的函数
        print("running on number:%s" % num)
        time.sleep(3)
    
    #1、target=sayhi :sayhi是定义的一个函数的名字
    #2、args=(1,)    : 括号内写的是函数的参数
    t1 = threading.Thread(target=sayhi, args=(1,))    # 生成一个线程实例
    t2 = threading.Thread(target=sayhi, args=(2,))    # 生成另一个线程实例
    
    t1.start()                                        # 启动线程
    t2.start()                                        # 启动另一个线程
    
    print(t1.getName())                               # 获取线程名
    print(t2.getName())
    
    直接调用
    import threading
    import time
    
    class MyThread(threading.Thread):
        def __init__(self,num):
            threading.Thread.__init__(self)
            self.num = num
    
        def run(self):#定义每个线程要运行的函数
            print("running on number:%s" %self.num)
            time.sleep(3)
    
    if __name__ == '__main__':
        t1 = MyThread(1)
        t2 = MyThread(2)
        t1.start()
        t2.start()
    
    继承式调用

      2、for循环同时启动多个线程

          说明:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和

    import threading
    import time
    
    def sayhi(num): #定义每个线程要运行的函数
        print("running on number:%s" %num)
        time.sleep(3)
    for i in range(50):
        t = threading.Thread(target=sayhi,args=('t-%s'%i,))
        t.start()
    
    for循环启动多个线程

       3、t.join(): 实现所有线程都执行结束后再执行主线程

          说明:在4中虽然可以实现50个线程同时并发执行,但是主线程不会等待子线程结束在这里我们可以使用t.join()指定等待某个线程结束的结果

    import threading
    import time
    start_time = time.time()
    
    def sayhi(num): #定义每个线程要运行的函数
        print("running on number:%s" %num)
        time.sleep(3)
    
    t_objs = []    #将进程实例对象存储在这个列表中
    for i in range(50):
        t = threading.Thread(target=sayhi,args=('t-%s'%i,))
        t.start()          #启动一个线程,程序不会阻塞
        t_objs.append(t)
    print(threading.active_count())    #打印当前活跃进程数量
    for t in t_objs: #利用for循环等待上面50个进程全部结束
        t.join()     #阻塞某个程序
    print(threading.current_thread())    #打印执行这个命令进程
    
    print("----------------all threads has finished.....")
    print(threading.active_count())
    print('cost time:',time.time() - start_time)
    
    t.join() 主线程等待子线程

      4、setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出

    import threading
    import time
    start_time = time.time()
    
    def sayhi(num): #定义每个线程要运行的函数
        print("running on number:%s" %num)
        time.sleep(3)
    for i in range(50):
        t = threading.Thread(target=sayhi,args=('t-%s'%i,))
        t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
        t.start()          #启动一个线程,程序不会阻塞
    print('cost time:',time.time() - start_time)
    
    守护线程

      

        注:因为刚刚创建的线程是守护线程,所以主线程结束后子线程就结束了,运行时间不是3秒而是0.01秒

      5、GIL锁和用户锁(Global Interpreter Lock 全局解释器锁    

         1.全局解释器锁:保证同一时间仅有一个线程对资源有操作权限

            作用:在一个进程内,同一时刻只能有一个线程通过GIL锁 被CUP调用,切换条件:I/O操作、固定时间(系统决定)

            说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多

            1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL

            2)GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程

            3)为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据

            4)python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口

            5)但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷   

          2. 用户锁:线程锁(互斥锁Mutex)  :当前线程还未操作完成前其他所有线程都无法对其操作,即使已经释放了GIL锁

            1. 在有GIL锁时为何还需要用户锁

              1)GIL锁只能保证同一时间只能有一个线程对某个资源操作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以操作了

            2. 线程锁的原理

              1)当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作

              2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题

    import time
    import threading
    lock = threading.Lock()          #1 生成全局锁
    def addNum():
        global num                  #2 在每个线程中都获取这个全局变量
        print('--get num:',num )
        time.sleep(1)
        lock.acquire()              #3 修改数据前加锁
        num  -= 1                   #4 对此公共变量进行-1操作
        lock.release()              #5 修改后释放
    
    用户锁使用举例

          3. 在有GIL的情况下执行 count = count + 1 会出错原因解析,用线程锁解决方法

    # 1)第一步:count = 0   count初始值为0
            # 2)第二步:线程1要执行对count加1的操作首先申请GIL全局解释器锁
            # 3)第三步:调用操作系统原生线程在操作系统中执行
            # 4)第四步:count加1还未执行完毕,时间到了被要求释放GIL
            # 5)第五步:线程1释放了GIL后线程2此时也要对count进行操作,此时线程1还未执行完,所以count还是0
            # 6)第六步:线程2此时拿到count = 0后也要对count进行加1操作,假如线程2执行很快,一次就完成了
            #    count加1的操作,那么count此时就从0变成了1
            # 7)第七步:线程2执行完加1后就赋值count=1并释放GIL
            # 8)第八步:线程2执行完后cpu又交给了线程1,线程1根据上下文继续执行count加1操作,先拿到GIL
            #    锁,完成加1操作,由于线程1先拿到的数据count=0,执行完加1后结果还是1
            # 9)第九步:线程1将count=1在次赋值给count并释放GIL锁,此时连个线程都对数据加1,但是值最终是1
    
    出错原因分析

          1、使用线程锁解决上面问题的原理

              1) 在GIL锁中再加一个线程锁,线程锁是用户层面的锁

              2) 线程锁就是一个线程在对数据操作前加一把锁,防止其他线程复制或者操作这个数据

              3) 只有这个线程对数据操作完毕后才会释放这个锁,其他线程才能操作这个数据

          2、定义一个线程锁非常简单只用三步:

              第一步:  lock = threading.Lock()                        #定义一把锁

              第二步:  lock.acquire()                                        #对数据操作前加锁防止数据被另一线程操作

              第三步:  lock.release()                                         #对数据操作完成后释放锁

       6、死锁

          1. 死锁定义

            两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

          2. 死锁举例

            1. 启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),抢到了B锁,
                在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待
            2. thread1执行完func1函数,然后执行func2函数,此时thread1拿到B锁,然后执行time.sleep(2),此时不会释放B锁
            3. 在thread1执行func2的同时thread2开始执行func1获取到了A锁,然后继续要获取B锁
            4. 不幸的是B锁还被thread1占用,thread1占用B锁时还需要同时获取A锁才能向下执行,但是此时发现A锁已经被thread2暂用,这样就死锁了

    from threading import Thread,Lock
    import time
    mutexA=Lock()
    mutexB=Lock()
    
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
        def func1(self):
            mutexA.acquire()
            print('33[41m%s 拿到A锁33[0m' %self.name)
    
            mutexB.acquire()
            print('33[42m%s 拿到B锁33[0m' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def func2(self):
            mutexB.acquire()
            print('33[43m%s 拿到B锁33[0m' %self.name)
            time.sleep(2)
    
            mutexA.acquire()
            print('33[44m%s 拿到A锁33[0m' %self.name)
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(2):
            t=MyThread()
            t.start()
    
    # 运行结果:输出下面结果后程序卡死,不再向下进行了
    # Thread-1 拿到A锁
    # Thread-1 拿到B锁
    # Thread-1 拿到B锁
    # Thread-2 拿到A锁
    
    产生死锁代码

      7、递归锁:lock = threading.RLock()  解决死锁问题

          1. 递归锁的作用是同一线程中多次请求同一资源,但是不会参数死锁。
          2. 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
          3. 直到一个线程所有的acquire都被release,其他的线程才能获得资源。

    from threading import Thread,Lock,RLock
    import time
    
    mutexA=mutexB=RLock()
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到A锁' %self.name)
    
            mutexB.acquire()
            print('%s 拿到B锁' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到B锁' % self.name)
            time.sleep(0.1)
            mutexA.acquire()
            print('%s 拿到A锁' % self.name)
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(5):
            t=MyThread()
            t.start()
    # 下面是运行结果:不会产生死锁
    # Thread-1 拿到A锁
    # Thread-1 拿到B锁
    # Thread-1 拿到B锁
    # Thread-1 拿到A锁
    # Thread-2 拿到A锁
    # Thread-2 拿到B锁
    # Thread-2 拿到B锁
    # Thread-2 拿到A锁
    # Thread-4 拿到A锁
    # Thread-4 拿到B锁
    # Thread-4 拿到B锁
    # Thread-4 拿到A锁
    # Thread-3 拿到A锁
    # Thread-3 拿到B锁
    # Thread-3 拿到B锁
    # Thread-3 拿到A锁
    # Thread-5 拿到A锁
    # Thread-5 拿到B锁
    # Thread-5 拿到B锁
    # Thread-5 拿到A锁
    
    如果使用RLock代替Lock,则不会发生死锁

      8、Semaphore(信号量)

          1. 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据
          2. 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
          3. 作用就是同一时刻允许运行的线程数量

    # import threading,time
    # def run(n):
    #     semaphore.acquire()
    #     time.sleep(1)
    #     print("run the thread: %s
    " %n)
    #     semaphore.release()
    # 
    # if __name__ == '__main__':
    #     semaphore  = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
    #     for i in range(22):
    #         t = threading.Thread(target=run,args=(i,))
    #         t.start()
    # 
    # while threading.active_count() != 1:
    #     pass #print threading.active_count()
    # else:
    #     print('----all threads done---')
    
    
    # 代码结果说明:这里可以清晰看到运行时0-4是同时运行的没有顺序,而且是前五个,
    # 表示再semaphore这个信号量的定义下程序同时仅能执行5个线程
    
    信号量举例

       9、events总共就只有四个方法

          1. event.set()          : 设置标志位

          2. event.clear()       : 清除标志位

          3. event.wait()        : 等待标志被设定

          4. event.is_set()     : 判断标志位是否被设定

    import time,threading
    
    event = threading.Event()
    #第一:写一个红绿灯的死循环
    def lighter():
        count = 0
        event.set()               #1先设置为绿灯
        while True:
            if count > 5 and count <10:      #2改成红灯
                event.clear()          #3把标志位清了
                print("red light is on.....")
            elif count > 10:
                event.set()            #4再设置标志位,变绿灯
                count = 0
            else:
                print("green light is on.....")
            time.sleep(1)
            count += 1
    
    #第二:写一个车的死循环
    def car(name):
        while True:
            if event.is_set():         #设置了标志位代表绿灯
                print("[%s] is running"%name)
                time.sleep(1)
            else:
                print('[%s] sees red light, waiting......'%name)
                event.wait()
                print('[%s] green light is on,start going.....'%name)
    
    light = threading.Thread(target=lighter,)
    light.start()
    car1 = threading.Thread(target=car,args=("Tesla",))
    car1.start()
    
    events(红绿灯例子)

    协程

      1、什么是协程(进入上一次调用的状态)

          1. 协程,又称微线程,纤程,协程是一种用户态的轻量级线程。

          2. 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,

          3. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈

          4. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态

          5. 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)

       2、协程的好处

          1. 无需线程上下文切换的开销(可以理解为协程切换就是在不同函数间切换,不用像线程那样切换上下文CPU)

          2. 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突

          3. 用法最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

      3、协程缺点

          1. 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上

          2. 线程阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

      4、使用yield实现协程相同效果

    import time
    import queue
    
    def consumer(name):
        print("--->starting eating baozi...")
        while True:
            new_baozi = yield  # 只要遇到yield程序就返回,yield还可以接收数据
            print("[%s] is eating baozi %s" % (name, new_baozi))
            time.sleep(1)
    
    def producer():
        r = con.__next__()  # 直接调用消费者的__next__方法
        r = con2.__next__()  # 函数里面有yield第一次加括号调用会变成一个生成器函数不执行,运行next才执行
        n = 0
        while n < 5:
            n += 1
            con.send(n)  # send恢复生成器同时并传递一个值给yield
            con2.send(n)
            print("33[32;1m[producer]33[0m is making baozi %s" % n)
    
    if __name__ == '__main__':
        con = consumer("c1")
        con2 = consumer("c2")
        p = producer()
    
    yield模拟实现协程效果

      5、协程为何能处理大并发1:Greenlet遇到I/O手动切换

          1. 协程之所以快是因为遇到I/O操作就切换(最后只有CPU运算)

          2. 这里先演示用greenlet实现手动的对各个协程之间切换

          3. 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换

    from greenlet import greenlet
    
    def test1():
        print(12)       #4 gr1会调用test1()先打印12
        gr2.switch()    #5 然后gr2.switch()就会切换到gr2这个协程
        print(34)       #8 由于在test2()切换到了gr1,所以gr1又从上次停止的位置开始执行
        gr2.switch()    #9 在这里又切换到gr2,会再次切换到test2()中执行
    
    def test2():
        print(56)       #6 启动gr2后会调用test2()打印56
        gr1.switch()    #7 然后又切换到gr1
        print(78)       #10 切换到gr2后会接着上次执行,打印78
    
    gr1 = greenlet(test1)    #1 启动一个协程gr1
    gr2 = greenlet(test2)    #2 启动第二个协程gr2
    gr1.switch()             #3 首先gr1.switch() 就会去执行gr1这个协程
    
    Greenlet遇到I/O手动切换

      6、协程为何能处理大并发2:Gevent遇到I/O自动切换

          1. Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程

          2. 在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程

          3. Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

          4. Gevent原理是只要遇到I/O操作就会自动切换到下一个协程

      7、Gevent实现简单的自动切换小例子

        注:在Gevent模仿I/O切换的时候,只要遇到I/O就会切换,哪怕gevent.sleep(0)也要切换一次

    import gevent
    
    def func1():
        print('33[31;1m第一次打印33[0m')
        gevent.sleep(2)          # 为什么用gevent.sleep()而不是time.sleep()因为是为了模仿I/O
        print('33[31;1m第六次打印33[0m')
    
    def func2():
        print('33[32;1m第二次打印33[0m')
        gevent.sleep(1)
        print('33[32;1m第四次打印33[0m')
    
    def func3():
        print('33[32;1m第三次打印33[0m')
        gevent.sleep(1)
        print('33[32;1m第五次打印33[0m')
    
    gevent.joinall([            # 将要启动的多个协程放到event.joinall的列表中,即可实现自动切换
        gevent.spawn(func1),    # gevent.spawn(func1)启动这个协程
        gevent.spawn(func2),
        gevent.spawn(func3),
    ])
    
    # 运行结果:
    # 第一次打印
    # 第二次打印
    # 第三次打印
    # 第四次打印
    # 第五次打印
    # 第六次打印
    
    Gevent实现简单的自动切换小例子

      8、使用Gevent实现并发下载网页与串行下载网页时间比较

    from urllib import request
    import gevent,time
    from gevent import monkey
    monkey.patch_all()      #把当前程序所有的I/O操作给我单独做上标记
    
    def f(url):
        print('GET: %s' % url)
        resp = request.urlopen(url)
        data = resp.read()
        print('%d bytes received from %s.' % (len(data), url))
    
    #1 并发执行部分
    time_binxing = time.time()
    gevent.joinall([
            gevent.spawn(f, 'https://www.python.org/'),
            gevent.spawn(f, 'https://www.yahoo.com/'),
            gevent.spawn(f, 'https://github.com/'),
    ])
    print("并行时间:",time.time()-time_binxing)
    
    #2 串行部分
    time_chuanxing = time.time()
    urls = [
            'https://www.python.org/',
            'https://www.yahoo.com/',
            'https://github.com/',
                                            ]
    for url in urls:
        f(url)
    print("串行时间:",time.time()-time_chuanxing)
    
    # 注:为什么要在文件开通使用monkey.patch_all()
    # 1. 因为有很多模块在使用I / O操作时Gevent是无法捕获的,所以为了使Gevent能够识别出程序中的I / O操作。
    # 2. 就必须使用Gevent模块的monkey模块,把当前程序所有的I / O操作给我单独做上标记
    # 3.使用monkey做标记仅用两步即可:
          第一步(导入monkey模块):  from gevent import monkey
          第二步(声明做标记)    :   monkey.patch_all()
    
    并行串行时间比较

        说明:monkey.patch_all()猴子补丁作用

          1)用过gevent就会知道,会在最开头的地方gevent.monkey.patch_all();
          2)作用是把标准库中的thread/socket等给替换掉.这样我们在后面使用socket的时候可以跟平常一样使用,无需修改任何代码,但是它变成非阻塞的了.

      9、通过gevent自己实现单线程下的多socket并发

    import gevent
    from gevent import socket,monkey     #下面使用的socket是Gevent的socket,实际测试monkey没用
    # monkey.patch_all()
    
    def server(port):
        s = socket.socket()
        s.bind(('0.0.0.0',port))
        s.listen(5)
        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 e:
            print(e)
        finally:
            conn.close()
    
    if __name__=='__main__':
        server(8001)
    
    server端
    import socket
    HOST = 'localhost'    # The remote host
    PORT = 8001           # The same port as used by the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    while True:
        msg = bytes(input(">>:"),encoding="utf8").strip()
        if len(msg) == 0:continue
        s.sendall(msg)
        data = s.recv(1024)
        print('Received', repr(data))
    s.close()
    
    client端

      10、协程本质原理

          1. 协程1通过os去读一个file,这个时候就是一个io操作,在调用os的接口前,就会有一个列表

          2. 协程1的这个操作就会被注册到这个列表中,然后就切换到其他协程去处理;

          3. 等待os拿到要读file后,也会把这个文件句柄放在这个列表中

          4. 然后等待在切换到协程1的时候,协程1就可以直接从列表中拿到数据,这样就可以实现不阻塞了

          5. epoll返回给协程的任务列表在内核态,协程在用户态,用户态协程是不能直接访问内核态的任务列表的,
              所以需要拷贝整个内核态的任务列表到用户态,供协程去访问和查询  

      11、epoll处理 I/O 请求原理

          1. epoll() 中内核则维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了。

          2. 在内核实现中 epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的回调函数实现的。

          3. 某个 sockfd 上的事件发生时,与它对应的回调函数就会被调用,来把这个 sockfd 加入链表,其他处于“空闲的”状态的则不会。

          4. epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销

          内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作

      12、select处理协程

          1. 拷贝所有的文件描述符给协程,不论这些任务的是否就绪,都会被返回

          2. 那么协程就只能for循环去查找自己的文件描述符,也就是任务列表,select的兼容性非常好,支持linux和windows

  • 相关阅读:
    基于Spring+SpringMVC实现AOP日志记录功能service注入异常为null的解决办法
    关于SpringBoot项目打包没有把依赖的jar包一起打包的解决办法
    JavaFx项目打包成exe,并集成Jre,使Java项目在任意机器运行
    常用正则表达式
    SqlServer 2005及其以上版本能用的查询数据的行号
    js 中的倒计时功能
    数据库删除重复列
    【转】svn文件清除批处理工具
    JS获取当前页面名称
    sql 去除重复记录
  • 原文地址:https://www.cnblogs.com/8lala/p/12449224.html
Copyright © 2020-2023  润新知