zoukankan      html  css  js  c++  java
  • 进程线程

     

    进程与线程之间的关系

    线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

    进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

    进程和线程的目的:提高执行效率


    1、单进程单线程,主进程、主线程
    2、自定义线程:
      主进程
        主线程
        子线程

    创建多进程,目的是为了利用CPU的多核,让CPU同时执行多个任务。

    进程:
    优点:同时利用多个CPU,能够同时进行多个操作
    缺点:耗费资源(创建时重新开辟内存空间)

    线程:
    优点:共享同一进程内存,IO操作时,不依赖CPU,可并发操作
    缺点:抢占资源

    进程、线程越多越好吗?
    进程不是越多越好,一般而言,CPU个数 = 进程个数
    线程数,依赖任务而定,不是越多越好,每次记录线程的请求信息,上下文切换耗时。


    IO密集型操作(不用CPU):多线程
    计算密集型操作(使用CPU):多进程

    python的进程上有个GIL 全局解释性锁,这个会造成,一个进程的多个线程,不能同时使用多个cpu,而是cpu每次只能选一个线程执行,因此,多线程在cpu执行的是无效的。但是在I/O操作的时候是可以同步的,比如time.sleep就是io 操作,多线程,可以同时等待

      主线程

        比如我们写的py文件,执行的时候,所有代码是如何向下执行呢?肯定有个主线程的。

        再我们创建多线程时候,这些线程都是子线程,那肯定有个主线程的。

    进程和程序关系
      进程:程序实例,程序子集,有所谓生命周期,可以kill掉,比如你安装的word,是一个程序,你打开一个文档是一个进程,可以关掉。
      进程要想完成并发执行的功能,就要进程切换。进程切换,上下文切换,运行进程,说明在cpu的寄存器里面有数据了。

    一、Python的多线程或多进程概述

    Python的多线程或多进程的调度是通过操作系统的调度程序实现的。当一个线程或进程阻塞,例如等待IO时,该线程或进程被操作系统降低执行的优先级,CPU内核可以被分配用来执行其它有实际计算任务的线程或进程。


    Python的线程或进程架构:
    “Multi-threading”

    线程存在于进程之内。一个进程可以包含多个线程,但通常包含至少一个线程,这个线程被称为主线程。在一个进程内的线程共享进程的内存,所以进程内的不同线程的通信可以通过引用共享的对象来实现。不同的进程并不共享同一块内存,所以进程间的通信是通过其它接口如文件、sockets或特别分配的共享内存区域来实现的。


    当线程需要执行操作时,它请求操作系统的线程调度程序给其分配一些CPU时间。调度程序根据各种参数来将CPU的核心分配给等待的线程,调度程序的实现根据操作系统的不同而不同。同一个进程中运行的不同线程可能同时运行在不同的CPU核心上(但CPython例外)。

     

    Python的线程和GIL

    Python的CPython解释器包含一个全局解释器锁Global Interpreter Lock(GIL),它的存在确保了Python进程中同时只有一个线程可以执行,即使有多个CPU核心可用。所以CPython程序中的多线程并不能通过多个cpu核心并行执行。不过,即使是这样,在等待I/O时被阻塞的线程仍然被操作系统降低执行优先级并放入背景等待,以便让真正有计算任务的线程可以执行,下图简单地描述了这个过程:


    “Threading-GIL”

    上图中的Waiting for GIL状态是某个线程已经完成了I/O,在退出阻塞状态想要开始执行时,另外一个线程持有GIL,所以已经就绪的线程被强制等待。在很多的网络应用程序中,花在等待I/O上的时间比实际处理数据的时间要多得多。只要不是有非常大的并发连接数,由GIL导致的连接的线程的阻塞是相对较低的,所以对于这些网络服务程序,使用线程的方式实现并发连接,仍然是一个合适的架构。

     

    Python的进程与线程的比较

    对于操作系统来说,一个应用就是一个进程。比如打开一个浏览器,它是一个进程;打开一个记事本,它是一个进程。每个进程有它特定的进程号。他们共享操作系统的内存资源。进程是操作系统分配资源的最小单位。


    而对于每一个进程而言,比如一个视频播放器,它必须同时播放视频和音频,就至少需要同时运行两个“子任务”,进程内的这些子任务就是通过线程来完成。线程是计算机执行任务的最小单元。一个进程它可以包含多个线程,这些线程相互独立,同时又共享进程所拥有的资源。


    以使用OS资源的角度来说,进程比线程更加“重量级”,创建一个新的进程需要的时间比创建一个新线程来说要多,而且进程使用更多的内存资源。


    有一点需要注意的是,如果你需要执行一个计算任务密集的Python程序,最好是通过多进程来实现。因为如果程序中的每个线程都有繁重的计算任务,它们都要使用CPU,而由于GIL的存在,这些线程并不能真正的在不同的CPU核心上并行执行,所以这会严重降低程序整体的性能。

    Python多进程、多线程编程

     Python中的多线程一般是通过其内置的threading模块提供的接口来实现的,多进程是通过multiprocessing模块实现的。本篇的代码实例是基于Python3的。


    Python多进程编程

     

    Process类

    在Python中,通过multiprocessing模块内的Process类来创建新的进程。例如:

    from multiprocessing import Process
    import os
    
    def info(title):
        print(title)
        print('module name:', __name__)
        if hasattr(os, 'getppid'):  # only available on Unix
            print('parent process:', os.getppid())
        print('process id:', os.getpid())
    
    def f(name):
        info('function f')
        print('hello', name)
        
    if __name__ == '__main__':
        info('main line')
        p = Process(target=f, args=('bob',))
        p.start()
        p.join()
        
    """
    main line
    module name: __main__
    parent process: 2128
    process id: 3688
    function f
    module name: __mp_main__
    parent process: 3688
    process id: 6884
    hello bob
    """
    View Code

    通过Process类中的start方法来启动新的进程。有三种不同的启动方式:spawn,fork和forkserver。这三种方式的详细区别请参考官方文档

    Process类中的join方法的作用是等待子进程结束后再继续往下执行。

    进程间通信

    multiprocessing模块内包括两种进程间通信的方式:Queues和Pipes;同时也提供了其它方式在进程间共享状态,不过官方文档也提到在并发程序设计中尽量避免这种方式。


    先来看一下Queues的例子:

    from multiprocessing import Process, Queue
    def f(q):
        q.put([42, None, 'hello'])
    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=(q,))
        p.start()
        print(q.get())    # prints "[42, None, 'hello']"
        p.join()
        
    """
    [42, None, 'hello']
    """
    View Code

    multiprocessing模块中的Queue几乎是queue.Queue的克隆,并且Queues是线程和进程安全的,这意味着多个线程或进程访问同一个Queue实例时,不会出现数据混乱的状况。下面来看一个使用Pipe的例子:

    from multiprocessing import Process, Pipe
    def f(conn):
        conn.send([42, None, 'hello'])
        conn.close()
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe()
        p = Process(target=f, args=(child_conn,))
        p.start()
        print(parent_conn.recv())   # prints "[42, None, 'hello']"
        p.join()
        
    """
    [42, None, 'hello']
    """
    View Code

    Pipe方法返回一对连接对象,代表管道的两端,每个连接对象都有send()和recv()方法。如果不同的进程(或线程)在同一时刻试图在管道的同一端进行读写,数据可能会出现混乱,在不同端是没有问题的。Python中的对象大部分都不是进程(或线程)安全的,所以在不同的进程(或线程)操作同一对象时,需要使用同步机制。

    进程的同步机制

    进程的同步是通过Lock实现的。实例如下:

    from multiprocessing import Process, Lock
    def f(l, i):
        l.acquire()
        try:
            print('hello world', i)
        finally:
            l.release()
    if __name__ == '__main__':
        lock = Lock()
        for num in range(10):
            Process(target=f, args=(lock, num)).start()
    
    """
    hello world 1
    hello world 3
    hello world 0
    hello world 2
    hello world 7
    hello world 6
    hello world 4
    hello world 8
    hello world 9
    hello world 5
    """
    View Code

    使用进程池

    Pool类提供了一个批量创建多个子进程的方法,可以给定子进程数的上限,避免无限地消耗系统的资源。看一下官方文档的实例:

    from multiprocessing import Pool, TimeoutError
    import time
    import os
    def f(x):
        return x*x
    if __name__ == '__main__':
        # start 4 worker processes
        with Pool(processes=4) as pool:
            # print "[0, 1, 4,..., 81]"
            print(pool.map(f, range(10)))
            # print same numbers in arbitrary order
            for i in pool.imap_unordered(f, range(10)):
                print(i)
            # evaluate "f(20)" asynchronously
            res = pool.apply_async(f, (20,))      # runs in *only* one process
            print(res.get(timeout=1))             # prints "400"
            # evaluate "os.getpid()" asynchronously
            res = pool.apply_async(os.getpid, ()) # runs in *only* one process
            print(res.get(timeout=1))             # prints the PID of that process
            # launching multiple evaluations asynchronously *may* use more processes
            multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
            print([res.get(timeout=1) for res in multiple_results])
            # make a single worker sleep for 10 secs
            res = pool.apply_async(time.sleep, (10,))
            try:
                print(res.get(timeout=1))
            except TimeoutError:
                print("We lacked patience and got a multiprocessing.TimeoutError")
            print("For the moment, the pool remains available for more work")
        # exiting the 'with'-block has stopped the pool
        print("Now the pool is closed and no longer available")
        
        """
        [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81
    400
    8652
    [8652, 8652, 8652, 8652]
    We lacked patience and got a multiprocessing.TimeoutError
    For the moment, the pool remains available for more work
    Now the pool is closed and no longer available
        """
    View Code

    Python多线程编程

    Python中使用线程的方式有很多都与使用进程的方式类似,这里就不再列出实例代码。

    Thread类

    在Python中,通过threading模块内的Thread类来创建新的进程。通过Thread类中的start方法启动线程,join方法等待线程结束。

    线程间通信

    线程间最简单的通信方式是通过threading模块内的Event类。当然也可以通过共享对象来在线程间共享状态信息。由于queue.Queue是线程和进程安全的,所以它也是线程通信使用理想对象。其它对象大部分都不是线程(或进程)安全的,所以在不同的线程(或进程)操作同一对象时,需要使用同步机制。

     

    线程的同步机制

    threading模块中提供了Lock,RLock,Condition,Semaphore等类进行线程间的同步。

    关于threading模块中更详细的使用信息,请参考官方文档

    内容来源与参考:

    https://andyyoung01.github.io/2017/01/21/Python%E7%9A%84%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%A4%9A%E8%BF%9B%E7%A8%8B%E7%AE%80%E4%BB%8B-%E4%B8%8A/#Python的线程和GIL

    https://andyyoung01.github.io/2017/01/22/Python%E7%9A%84%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%A4%9A%E8%BF%9B%E7%A8%8B%E7%AE%80%E4%BB%8B-%E4%B8%8B/

     

    Python内的线程进程协程模块实现

    threading模块

    threading 模块建立在 _thread 模块之上。thread 模块以低级、原始的方式来处理和控制线程,而 threading 模块通过对 thread 进行二次封装,提供了更方便的 api 来处理线程。

    threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象,所以python的threading模块中还提供了Lock,Rlock,Condition,Event等常用类,它们在python中是独立于Tread模块的,但是却与线程紧密相关,不可分割。

    需要注意的是:python的线程中没有优先级、线程组,也不能被停止、暂停、恢复、中断,线程只能随着线程中的代码执行完毕而被销毁。在实现线程池的时候无法停止已经注入了方法且执行超时的线程。

    创建线程的两种方式

    第一种创建线程的方式 创建5个线程

    import threading
    import time
    
    def worker(num):
        time.sleep(3)
        print("Thread %d" % num)
        return
    
    for i in range(5):
        t = threading.Thread(target=worker, args=(i,))
        t.start()
        
    """
    Thread 0
    Thread 1
    Thread 2
    Thread 4
    Thread 3
    """
    View Code

    第二种创建线程的方式 创建5个线程

    import threading
    import time
    
    class MyThread(threading.Thread):
        def __init__(self, name):
            # threading.Thread.__init__(self)
            super(MyThread, self).__init__(target=self.fun, name="t %d" % i)
            self.name = name
    
        def fun(self):
            time.sleep(2)
            print("name %s thread %s" % (self.name, threading.current_thread().name))
    
    for i in range(5):
        t = MyThread(i)
        t.start()
        
    """
    name 0 thread 0
    name 2 thread 2
    name 1 thread 1
    name 4 thread 4
    name 3 thread 3
    """
    View Code

    thread线程类的方法说明

    t.start() : 激活线程,

    t.getName() : 获取线程的名称

    t.setName() : 设置线程的名称 

    t.name : 获取或设置线程的名称

    t.is_alive() : 判断线程是否为激活状态

    t.isAlive() :判断线程是否为激活状态

    t.setDaemon() 设置为后台线程或前台线程(默认:False);通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

    t.isDaemon() : 判断是否为守护线程

    t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。

    t.join() :逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义

    t.run() :线程被cpu调度后自动执行线程对象的run方法

    join 代码 

    import time
    import threading
    
    def printNum(a):
        print('num:',a)
        time.sleep(1)
    
    def ThreadTest(i):
        return threading.Thread(target=printNum, args=(999,))
    
    thread_arr = []
    for i in range(5):
        t = ThreadTest(i)
        thread_arr.append(t)
    
    for t in thread_arr:
        t.start()
    
    for t in thread_arr:
        t.join()
    
    print('finished')
    
    """
    num: 999
    num: 999
    num: 999
    num: 999
    num: 999
    finished
    """
    View Code

    多线程的说明

    threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组,线程也不能被停止、暂停、恢复、中断。

    threading模块提供的类:  
      Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

    threading 模块提供的常用方法: 
      threading.currentThread(): 返回当前的线程变量。 
      threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 
      threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

    threading 模块提供的常量:

      threading.TIMEOUT_MAX 设置threading全局超时时间。

    Thread是线程类,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run():

    import threading
    import time
    
    # 方法一:将要执行的方法作为参数传给Thread的构造方法
    def action(arg):
        time.sleep(1)
        print('the arg is:%s\r' % arg)
    
    for i in range(4):
        t = threading.Thread(target=action, args=(i,))
        t.start()
    
    print('main thread end!')
    
    """
    main thread end!
    the arg is:0
    the arg is:3
    """
    
    
    # 方法二:从Thread继承,并重写run()
    class MyThread(threading.Thread):
        def __init__(self, arg):
            super(MyThread, self).__init__()  # 注意:一定要显式的调用父类的初始化函数。
            self.arg = arg
    
        def run(self):  # 定义每个线程要运行的函数
            time.sleep(1)
            print('the arg is:%s\r' % self.arg)
    
    for i in range(4):
        t = MyThread(i)
        t.start()
    
    print('main thread end!')
    
    """
    main thread end!
    the arg is:1
    
    the arg is:2
    the arg is:3
    """
    View Code

    Lock、Rlock类

    由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。

    Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。

    可以认为Lock有一个锁定池,当线程请求锁定时,将线程置于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。

    RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。

    可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。

    简言之:Lock属于全局,Rlock属于线程。

    构造方法: 
    Lock(),Rlock(),推荐使用Rlock()

    实例方法: 
      acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。 
      release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

    线程锁threading.RLock和threading.Lock

    我们使用线程对数据进行操作的时候,如果多个线程同时修改某个数据,可能会出现不可预料的结果,为了保证数据的准确性,引入了锁的概念。

    例:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1,那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。

     View Code

     Lock与Rlock 对比

    import threading   
    lock = threading.Lock() #Lock对象   
    lock.acquire()   
    lock.acquire()  #产生了死锁。   
    lock.release()   
    lock.release()   
    
    
    import threading   
    rLock = threading.RLock()  #RLock对象   
    rLock.acquire()   
    rLock.acquire() #在同一线程内,程序不会堵塞。   
    rLock.release()   
    rLock.release()   
    
    lock vs rlock Code
    View Code

    未使用锁

    多次运行可能产生混乱。这种场景就是适合使用锁的场景。

    import threading
    import time
    
    gl_num = 0
    
    def show(arg):
        global gl_num
        time.sleep(0.00001)
        gl_num += 1
        print(gl_num)
    
    for i in range(5):
        t = threading.Thread(target=show, args=(i,))
        t.start()
    
    print('main thread stop')
    
    """
    1
    2
    3
    main thread stop
    4
    5
    """
    View Code

    使用锁

    全局变量在每次被调用时都要获得锁,才能操作,因此保证了共享数据的安全性。

    import threading
    import time
    
    gl_num = 0
    
    lock = threading.RLock()
    
    # 调用acquire([timeout])时,线程将一直阻塞,直到获得锁定或者直到timeout秒后(timeout参数可选)。
    # 返回是否获得锁。
    def Func():
        lock.acquire()
        global gl_num
        gl_num += 1
        time.sleep(0.00001)
        print(gl_num)
        lock.release()
    
    for i in range(5):
        t = threading.Thread(target=Func)
        t.start()
    
    """
    1
    2
    3
    4
    5
    """
    View Code

    Condition类


    条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。如,状态的改变或值的改变。

    Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。

    可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

    一个condition变量总是与某些类型的锁相联系,这个可以使用默认的情况或创建一个,当几个condition变量必须共享和同一个锁的时候,是很有用的。锁是conditon对象的一部分:没有必要分别跟踪。

    condition变量服从上下文管理协议:with语句块封闭之前可以获取与锁的联系。 acquire() 和 release() 会调用与锁相关联的相应的方法。

    其他和锁关联的方法必须被调用,wait()方法会释放锁,当另外一个线程使用 notify() or notify_all()唤醒它之前会一直阻塞。一旦被唤醒,wait()会重新获得锁并返回

    Condition(lock=None)

    构造方法: 
    Condition([lock/rlock])

    实例方法: 

    • acquire([timeout])/release(): 调用关联的锁的相应方法,给线程上锁/解锁。 
    • wait([timeout]): 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。 
    • notify(): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 
    • notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

    比较经典的例子是生产者与消费者模型,代码中写了两个类,Consumer和Producer,分别继承了Thread类,分别初始化这两个类获得了c和p对象,并启动这两个线程。则这两个线程去执行run方法(这里与Thread类内部的调度有关),定义了producer全局变量和condition对象为全局变量,当producer不大于1时,消费者线程被condition对象阻塞,不能继续消费(这里是不再递减),当producer不小于10时,生产者线程被condition对象阻塞,不再生产(这里是不再累加),代码在下面,拿去执行,断点一下就明白了。

    import threading
    import time
    
    condition = threading.Condition()
    products = 0
    
    class Producer(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            global condition, products
            while True:
                if condition.acquire():
                    if products < 10:
                        products += 1;
                        print("Producer(%s):deliver one, now products:%s" %(self.name, products))
                        condition.notify()
                    else:
                        print("Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products))
                        condition.wait();
                    condition.release()
                    time.sleep(2)
    
    class Consumer(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            global condition, products
            while True:
                if condition.acquire():
                    if products > 1:
                        products -= 1
                        print("Consumer(%s):consume one, now products:%s" %(self.name, products))
                        condition.notify()
                    else:
                        print("Consumer(%s):only 1, stop consume, products:%s" %(self.name, products))
                        condition.wait();
                    condition.release()
                    time.sleep(2)
    
    if __name__ == "__main__":
        for p in range(0, 2):
            p = Producer()
            p.start()
    
        for c in range(0, 10):
            c = Consumer()
            c.start()
    
    """
    Producer(Thread-1):deliver one, now products:1
    Producer(Thread-2):deliver one, now products:2
    Consumer(Thread-3):consume one, now products:1
    Consumer(Thread-4):only 1, stop consume, products:1
    Consumer(Thread-5):only 1, stop consume, products:1
    .....
    """
    View Code

    Event类


    通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。

    Event(事件)是最简单的线程通信机制之一:一个线程通知事件,其他线程等待事件。Event内置了一个初始为False的标志,当调用set()时设为True,调用clear()时重置为 False。wait()将阻塞线程至等待阻塞状态。

    Event其实就是一个简化版的 Condition。Event没有锁,无法使线程进入同步阻塞状态。

    构造方法: 
    Event()

    实例方法: 

    • isSet(): 当内置标志为True时返回True。 
    • set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。 
    • clear(): 将标志设为False。 
    • wait([timeout]): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。

    这是一个比较关键的类,它的意义在于可以控制属于同一个线程类的多个实例化对象,让他们同时阻塞或者执行。配合队列来实现一个线程池非常好用。

    !!线程之间交互  threading.Event方法  红灯 绿灯  信号标志位!!

    import threading
    import time
    import random
    
    def light():
        if not event.isSet():  #没有设置的话
            event.set()  # 设置绿灯
        count = 0  #计数器秒数
        while True:
            if count < 10:    #小于十秒 是绿灯
                print("\033[42;1m ------green light on ----\033[0m")
            elif count < 13:  #小于13秒 大于10秒 是黄灯
                print("\033[43;1m ------yellow light on ----\033[0m")
            elif count < 20:  #小于于20秒 有设置则取消
                if event.isSet():
                    event.clear()
                print("\033[41;1m ------red light on ----\033[0m")
            else:  #大于20 重新
                count = 0    #取消秒数计时
                event.set()   #重新变为绿灯
    
            time.sleep(1)
            count +=1
    
    def car(n):  # 第二个线程 车线程
        while 1:
            time.sleep(random.randrange(3))  #随机等待三秒
            if event.isSet():
                print("car [%s] is running..." % n)  #如果被设置了信号则是绿灯,该线程的车即可通过
            else:  #否则的话提示红灯
                print("car [%s] is waitting for the red light.." %n)
                event.wait()   #红灯的话,会在此处卡住,不往下执行
                print("Green light is on ,car %s is running......." %n)
    if __name__ == '__main__':  #下面是定义了两个线程  ,灯线程 车线程, threading.Event用来设置标着符号让两个线程交流
        event = threading.Event()
        Light = threading.Thread(target=light)
        Light.start()
        for i in range(3):
            t = threading.Thread(target=car,args=(i,))
            t.start()
    """
     ------green light on ----
    car [2] is running...
    car [0] is running...
     ------yellow light on ----
    car [1] is running...
    car [1] is running...
    car [2] is running...
    car [0] is running...
     ------red light on ----
    car [1] is waitting for the red light..
    ......
    """
    View Code

    queue模块

    Queue 就是对队列,它是线程安全的

    举例来说,我们去肯德基吃饭。厨房是给我们做饭的地方,前台负责把厨房做好的饭卖给顾客,顾客则去前台领取做好的饭。这里的前台就相当于我们的队列。

    这个模型也叫生产者-消费者模型。

    import queue
     
    q = queue.Queue(maxsize=0)  # 构造一个先进先出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。
     
    q.join()    # 等到队列为空的时候,再执行别的操作
    q.qsize()   # 返回队列的大小 (不可靠)
    q.empty()   # 当队列为空的时候,返回True 否则返回False (不可靠)
    q.full()    # 当队列满的时候,返回True,否则返回False (不可靠)
    
    q.put(item, block=True, timeout=None) #  将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置。为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后,如果队列无法给出放入item的位置,则引发 queue.Full 异常
    
    q.get(block=True, timeout=None) #   移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。
    
    q.put_nowait(item) #   等效于 put(item,block=False)
    q.get_nowait() #    等效于 get(item,block=False)
    View Code
  • 相关阅读:
    《数据结构与算法之8 求和函数》
    <C Primer Plus>12 switch and break continue
    <C Primer Plus>11 A Word-Count Program
    《数据结构与算法之7 顺序查找》
    <C Primer Plus>10 The Sequence points && and ||
    <C Primer Plus>9 Introducing getchar() and putchar()
    小米校园招聘 2017 编程题:号码分身
    华为笔试题 合唱队
    识别有效的IP地址和掩码并进行分类统计
    小米Git
  • 原文地址:https://www.cnblogs.com/ttrrpp/p/6737314.html
Copyright © 2011-2022 走看看