zoukankan      html  css  js  c++  java
  • 线程和协程

    线程

    初识线程:

    • 轻量级进程,直接被cpu调度
    • 不能独立存在的轻量级进程
    • 同一个进程中的多个线程之间的数据共享

    线程和进程的关系:

    线程和进程的区别可以归纳为以下4点:

    • 地址空间和其他资源(如打开文件):进程间相互独立,同一个进程的各线程间共享.某进程内的线程在其他进程不可见
    • 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信--需要进程同步和互斥手段的辅助,以保证数据的一致性
    • 调度和切换:线程上下文切换比进程上下文切换要快的多
    • 在多线程操作系统中,进程不是一个可执行的实体

    全局解释器锁GIL:

    • 全局解释器锁,是用来锁线程的,Cpython解释器提供的,导致了同一时刻只能有一个线程访问cpu

    python线程模块的选择:

      Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。   避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。 

      thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出

    threading模块

    线程的创建:

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.start()
        print('主线程')
    
    创建线程的方式1
    from threading import Thread
    import time
    class Sayhi(Thread):
        def __init__(self,name):
            super().__init__()
            self.name=name
        def run(self):
            time.sleep(2)
            print('%s say hello' % self.name)
    
    
    if __name__ == '__main__':
        t = Sayhi('egon')
        t.start()
        print('主线程')
    
    创建线程的方式2

    Thread类的其他方法:

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

    守护线程:

    无论在进程还是线程,都遵循:守护xx会等待主xx运行完毕后倍销毁.需要强调的是:运行完毕并非终止运行

    #1.对主进程来说,运行完毕指的是主进程代码运行完毕
    #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
    #1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
    #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.setDaemon(True) #必须在t.start()之前设置
        t.start()
    
        print('主线程')
        print(t.is_alive())
        '''
        主线程
        True
        '''

    锁:

    互斥锁:

    无论在相同的线程还是不同的线程,都只能连续acquire一次
    要想在acquire,必须先release

    死锁:

    所谓死锁:就是两个或者两个以上的进程或线程在执行的过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程成为死锁进程,如下就是死锁

    from threading import Lock as Lock
    import time
    mutexA=Lock()
    mutexA.acquire()
    mutexA.acquire()
    print(123)
    mutexA.release()
    mutexA.release()

    递归锁:

    在同一个进程中.可以无限次的acquire,但是要现在其他进程中也acquire,必须在自己的线程中添加和acquire次数相同的release

    from threading import RLock as Lock
    import time
    mutexA=Lock()
    mutexA.acquire()
    mutexA.acquire()
    print(123)
    mutexA.release()
    mutexA.release()

    信号量:

    • Semaphore管理一个内置的计数器,每当调用acquire()时内置的计数器-1,调用release()是内置计数器+1,计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
    from threading import Thread,Semaphore
    import threading
    import time
    # def func():
    #     if sm.acquire():
    #         print (threading.currentThread().getName() + ' get semaphore')
    #         time.sleep(2)
    #         sm.release()
    def func():
        sm.acquire()
        print('%s get sm' %threading.current_thread().getName())
        time.sleep(3)
        sm.release()
    if __name__ == '__main__':
        sm=Semaphore(5)
        for i in range(23):
            t=Thread(target=func)
            t.start()

    事件:

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    event.wait():如果 event.isSet()==False将阻塞线程;
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False。

    条件:

    使得线程等待,只有满足某条件是,才释放n个进程

    Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。
    线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程
    接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

    定时器:

    from threading import Timer
     
    def hello():
        print("hello, world")
     
    t = Timer(1, hello)
    t.start()  # after 1 seconds, "hello, world" will be printed

    线程队列:

    queue队列 :使用import queue,用法与进程Queue一样

    queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

    class queue.Queue(maxsize=0) #先进先出
    import queue
    
    q=queue.Queue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(先进先出):
    first
    second
    third
    '''
    
    先进先出

    class queue.LifoQueue(maxsize=0) #last in fisrt out

    import queue
    
    q=queue.LifoQueue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(后进先出):
    third
    second
    first
    '''
    
    后进先出

    class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

    import queue
    
    q=queue.PriorityQueue()
    #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    q.put((20,'a'))
    q.put((10,'b'))
    q.put((30,'c'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(数字越小优先级越高,优先级高的优先出队):
    (10, 'b')
    (20, 'a')
    (30, 'c')
    '''
    
    优先级队列

    concurrent.futures模块:

    #1 介绍
    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor: 进程池,提供异步调用
    Both implement the same interface, which is defined by the abstract Executor class.
    
    #2 基本方法
    #submit(fn, *args, **kwargs)
    异步提交任务
    
    #map(func, *iterables, timeout=None, chunksize=1) 
    取代for循环submit的操作
    
    #shutdown(wait=True) 
    相当于进程池的pool.close()+pool.join()操作
    wait=True,等待池内所有任务执行完毕回收完资源后才继续
    wait=False,立即返回,并不会等待池内的任务执行完毕
    但不管wait参数为何值,整个程序都会等到所有任务执行完毕
    submit和map必须在shutdown之前
    
    #result(timeout=None)
    取得结果
    
    #add_done_callback(fn)
    回调函数

    协程

    协程介绍:

    • 协程是单线程下的并发,又称微线程,纤程
    • 协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的
    • 协程能够实现在一条线程上的多个任务互相切换
    • 为了提高工作效率,用户可以控制在一个任务中遇到io就切换

    对比操作系统控制线程的切换,用户在单线程内控制协程的切换

    优点如下:

    1.协程的切换开销更小,属于用户级别的切换,操作系统完全感知不到,因而更加        轻量级
    2.单线程内就可以实现并发的效果,最大限度利用cpu

    缺点如下:

    1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    2.协程指的是单个线程,因而一旦出现协程阻塞,将会阻塞整个线程

    greenlet模块:

    from greenlet import greenlet
    
    def eat(name):
        print('%s eat 1' %name)
        g2.switch('egon')
        print('%s eat 2' %name)
        g2.switch()
    def play(name):
        print('%s play 1' %name)
        g1.switch()
        print('%s play 2' %name)
    
    g1=greenlet(eat)
    g2=greenlet(play)
    
    g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要
    
    greenlet实现状态切换

    greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题

    gevent模块:

    Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度

    g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
    
    g2=gevent.spawn(func2)
    
    g1.join() #等待g1结束
    
    g2.join() #等待g2结束
    
    #或者上述两步合作一步:gevent.joinall([g1,g2])
    
    g1.value#拿到func1的返回值
    
    用法介绍

    from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

    或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

  • 相关阅读:
    绪论-1.1.2机器学习
    Django从 URL获取参数的几种方式
    conda创建虚拟换件安装包时报错“无法定位程序输入点OPENSSL_sk_new_reserve于动态链接库C:Users...libssl-1_1-x64.dll”
    执行python manage.py makemigrations出错
    Linux下conda虚拟环境
    云服务器安装mysql
    2020牛客暑期多校训练营(第五场)
    2020牛客暑期多校训练营(第四场)
    2020牛客暑期多校训练营(第三场)
    All with Pairs
  • 原文地址:https://www.cnblogs.com/pythonz/p/10932883.html
Copyright © 2011-2022 走看看