协程

  1. 线程

    • 多线程抢占资源:只能让其串行
    1. 互斥锁

    2. 队列(三种)

      1566893653899

    • 先进先出(Queue)
    import queue
    q = queue.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    #q.put(4) 阻塞
    print(p.get(block = False))
    print(p.get())
    print(p.get())
    q.get(timeout = 2) #阻塞2s,还没有值就会报错
    
    
    • 先进后出,栈
    import queue
    q = queue.LifoQueue(3)
    q.put(1)
    q.put(2)
    q.put('alex')
    print(q.get())
    print(p.get())
    print(p.get())
    
    • 优先级队列
    import queue
    q = queue.PriorityQueue(4)
    q.put((1,'元宝'))
    q.put((0,'Gouhu'))
    q.put((-1,'甘冈'))
    
  2. 事件(event)

    1. 版本一:程序中其他线程需要通过判断某一线程的状态来确定自己下一步的操作
    from threading import Thread
    # from threading import current_thread
    # import time
    # flag = False
    # def check():
    #     print(f'{current_thread().name} 监测服务器是否开启... ')
    #     time.sleep(3)
    #     global flag
    #     flag = True
    #     print('服务器已经开启...')
    # def connect():
    #     while 1:
    #         print(f'{current_thread().name} 等待连接...')
    #         time.sleep(0.5)
    #         if flag:
    #             print(f'{current_thread().name} 连接成功...')
    #             break
    # t1 = Thread(target=check,)
    # t2 = Thread(target=connect,)
    # t1.start()
    # t2.start()
    
    1. event
    from threading import Thread
    from threading import Event
    from threading import current_thread
    import time
    event = Event()
    def check():
        print(f'{current_thread().name} 监测服务器是否开启...')
        time.sleep(3)
        print(event.is_set())#判断event内部标志位是否更改
        event.set()#更改event内部的标志位
        print(event.is_set())
        print(f'{current_thread().name} 服务器已经开启...')
    def connect():
        print(f'{current_thread().name} 等待连接...')
        event.wait()#等待event内部标志位改变,再进行下面操作
        event.wait(1)#不报错,等待一秒,一秒之后如果还没有set直接进行下一步操作
        print(f'{current_thread().name} 连接成功...')
    t1 = Thread(target=check,)
    t2 = Thread(target=connect,)
    t1.start()
    t2.start()
    

    练习题:

    一个线程监测服务器是否开始,
    # 另个一线程判断如果开始了,则显示连接成功,此线程只尝试连接3次,1s 一次,如果超过3次,还没有连接成功,则显示连接失败.
    from threading import Thread
    from threading import current_thread
    from threading import Event
    event = Event()
    import time
    def check():
    	print(f'{current_thread().name} 监测服务器连接状态...')
    	time.sleep(3)
    	event.set()
    	print(f'{current_thread().name}服务器已经开启...' )
    def connect():
    	count = 1
    	while not event.is_set():
    		if count == 4:
    			print('连接失败...')
    			break
    		else:
    			print(f'{current_thread().name} 尝试连接...')
    			count += 1
    			event.wait(1)
    	print(f'{current_thread().name} 连接成功...')
    t1 = Thread(target = check,)
    t2 = Thread(target = connect,)
    t1.start()
    t2.start()
    
    版本二:
    from threading import Thread
    # from threading import current_thread
    # from threading import Event
    # import time
    #
    # event = Event()
    # def check():
    #     print(f'{current_thread().name} 监测服务器是否开启...')
    #     time.sleep(4)
    #     event.set()
    #     print('服务器已经开启...')
    #
    # def connect():
    #     count = 1
    #     while not event.is_set():
    #         if count == 4:
    #             print('连接次数过多,已断开')
    #             break
    #         event.wait(1)
    #         print(f'{current_thread().name} 尝试连接{count}次')
    #         count += 1
    #     else:
    #         print(f'{current_thread().name} 连接成功...')
    #
    # t1 = Thread(target=check,)
    # t2 = Thread(target=connect,)
    # t1.start()
    # t2.start()
    
  3. 协程:一个线程并发的处理任务

    • 串行: 一个线程执行一个任务,执行完毕之后,执行下一个任务.

    • 并行: 多个cpu执行多个任务, 4个cpu 执行4个任务.

    • 并发: 一个cpu执行多个任务,看起来像是同时运行.并发真正的核心: 切换并且保持状态.

    • 多线程的并发: 3个线程处理10个任务,如果线程1处理的这个任务,遇到阻塞,cpu被操作系统切换到另一个线
      程,

    • 一个线程能否并发的处理任务??? 一个线程处理三个任务.
      单个cpu: 10个任务,让你给我并发的执行这个10个任务:

      1. 方式一:开启多进程并发执行, 操作系统切换+保持状态.

      2. 方式二:开启多线程并发执行,操作系统切换+保持状态.

      3. 方式三:开启协程并发的执行, 自己的程序 把控着cpu 在3个任务之间来回切换+保持状态.
        对3详细解释: 协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程.)

      4. 协程方式最好,为什么?

        ​ 开销小.

        ​ 运行速度快.

        ​ 协程会长期霸占cpu只执行我程序里面的所有任务.

        ​ 并发的本质:就是切换+保持状态.协程处理IO密集型, 计算密集型,还是串行好.什么是协程?

        ​ 单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.
        ​ 协程的特点:必须在只有一个单线程里实现并发

        ​ 修改共享数据不需加锁

        ​ 用户程序里自己保存多个控制流的上下文栈(保持状态)

      5. 附加:一个协程遇到IO操作自动切换到其它协程
        工作中:一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

    之前学的代码:有没有切换.
    
    # 切换
    # def func1():
    #     print('in func1')
    #
    # def func2():
    #     print('in func2')
    #     func1()
    #     print('end')
    #
    # func2()
    
    # 切换 + 保持状态
    
    # def gen():
    #     while 1:
    #         yield 1
    #
    # def func():
    #     obj = gen()
    #     for i in range(10):
    #         next(obj)
    # func()
    
    # 上面的例子遇到IO不能自动切换
    # import time
    # def gen():
    #     while 1:
    #         yield 1
    #         time.sleep(0.5)
    #
    # def func():
    #     obj = gen()
    #     for i in range(10):
    #         next(obj)
    # func()
    
    #
    # 切换 +保持状态(遇到IO不会主动切换)
    
    # from greenlet import greenlet
    # import time
    # def eat(name):
    #     print('%s eat 1' %name)  # 2
    #     # g2.switch('taibai')  # 3
    #     time.sleep(3)
    #     print('%s eat 2' %name)  # 6
    #     g2.switch()  # 7
    #
    # def play(name):
    #     print('%s play 1' %name)  # 4
    #     g1.switch()  # 5
    #     print('%s play 2' %name)  # 8
    #
    # g1=greenlet(eat)
    # g2=greenlet(play)
    #
    # g1.switch('taibai')  # 1 切换到eat任务
    import time
    
    # 协程
    # 模拟的阻塞,不是真正的阻塞
    import gevent
    import time
    from threading import current_thread
    def eat(name):
        print(f'{name} eat 1')
        print(current_thread().name)
        gevent.sleep(2)
        # time.sleep(2)###不会切换
        print(f'{name} eat 2')
    def play(name):
        print(f'{name} play 1')
        print(current_thread().name)
        gevent.sleep(1)
        # time.sleep(1)
        print(f'{name} play 2')
    g1 = gevent.spawn(eat,'egon')
    g2 = gevent.spawn(play,name = 'egon')
    print(f'主{current_thread().name}')
    gevent.joinall([g1,g2])
    
    主MainThread
    egon eat 1
    MainThread
    egon play 1
    MainThread
    egon play 2
    egon eat 2
    
    # 最终版本:
    import gevent
    from gevent import monkey
    monkey.patch_all()  # 打补丁: 将下面的所有的任务的阻塞都打上标记
    def eat(name):
        print(f'{name} eat 1')
        time.sleep(2)
        print(f'{name} eat 2')
    def play(name):
        print(f'{name} play 1')
        time.sleep(1)
        print(f'{name} play 2')
    g1 = gevent.spawn(eat,'egon')
    g2 = gevent.spawn(play,name = 'egon')
    gevent.joinall([g1,g2])
    
    egon eat 1
    egon play 1
    egon play 2
    egon eat 2
原文地址:https://www.cnblogs.com/-777/p/11421068.html