• 033_线程


    操作系统线程理论

    1,线程概念的引入背景

      1.1,有了进程为什么要有线程

      进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

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

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

      如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。

      现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。

    1.2,线程的出现

      60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
      因此在80年代,出现了能独立运行的基本单位——线程(Threads)
      注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
         每一个进程中至少有一个线程。 
     
     2,进程和线程的关系
     
      线程与进程的区别可以归纳为以下4点:
      1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
      2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
      3)调度和切换:线程上下文切换比进程上下文切换要快得多。
      4)在多线程操作系统中,进程不是一个可执行的实体。
     
     3,线程的特点
      在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
      1)轻型实体
      线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
      线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
    TCB包括以下信息:
    (1)线程状态。
    (2)当线程不运行时,被保存的现场资源。
    (3)一组执行堆栈。
    (4)存放每个线程的局部变量主存区。
    (5)访问同一个进程中的主存和其它资源。
    用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
    TCB包括以下信息
      2)独立调度和分派的基本单位。
      在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
      3)共享进程资源。
      线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
      4)可并发执行。
      在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
     
    4,使用线程的实际场景
      开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
    5,内存中的线程

      多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。

      而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

      不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。

      类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。

      线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:

      1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程

      2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?

      因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。

    6,用户级线程和内核级线程(了解)

      线程的实现可以分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程

      6.1,用户级线程

      内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu。

      

      在用户空间模拟操作系统对进程的调度,来调用一个进程中的线程,每个进程中都会有一个运行时系统,用来调度线程。此时当该进程获取cpu时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行。

      6.2,内核级线程

       内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用smp,即利用多核cpu。windows线程就是这样的。

      

       6.3,用户级与内核级线程的对比

    1 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
    2 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
    3 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
    4 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
    5 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
    用户级线程和内核级线程的区别
    优点:当有多个处理机时,一个进程的多个线程可以同时执行。
    缺点:由内核进行调度。
    内核线程的优缺点
    优点:
    线程的调度不需要内核直接参与,控制简单。
    可以在不支持线程的操作系统中实现。
    创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
    允许每个进程定制自己的调度算法,线程管理比较灵活。
    线程能够利用的表空间和堆栈空间比内核级线程多。
    同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。
    缺点:
    资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
    用户级线程的优缺点

        用户级:一次只能调用一个线程,多线程并发,是由分时复用实现。

        内核级:一个进程的多线程可以同时执行

      6.4,混合实现

        用户级与内核级的多路复用,内核同一调度内核线程,每个内核线程对应n个用户线程

      

     

    线程和python——理论知识

    1,全局解释器锁GIL

      Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
      对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

      在多线程环境中,Python 虚拟机按以下方式执行:

      a、设置 GIL;

      b、切换到一个线程去运行;

      c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

      d、把线程设置为睡眠状态;

      e、解锁 GIL;

      d、再次重复以上所有步骤。
      在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

    2,python线程模块的选择

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

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

     

    threading模块

      multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍(官方链接

    1,线程的创建Threading.Thread类

      1.1,使用Thread开启线程,是异步的

    import os
    from threading import Thread
    
    def func():
        print('hello world',os.getpid())
    
    t = Thread(target = func)
    t.start()
    print(os.getpid())
    

      1.2,等待子线程执行完,在执行主线程后面的,且子线程还是异步。

    # import os
    # import time
    # from threading import Thread
    #
    # def func():    # 子线程
    #     time.sleep(1)
    #     print('hello world',os.getpid())
    # thread_lst = []
    # for i in range(10):
    #     t = Thread(target=func)
    #     t.start()
    #     thread_lst.append(t)
    # [t.join() for t in thread_lst]
    # print(os.getpid())   # 主线程
    

      1.3,开启线程的另一种方式

        计算开启的线程数量,多个子线程 共享 这个数据

    # import time
    # import os
    # import time
    # from threading import Thread
    #
    # class MyThread(Thread):
    #     count = 0         # 静态属性   # 各线程共享
    #     def __init__(self,arg1,arg2):
    #         super().__init__()
    #         self.arg1 = arg1
    #         self.arg2 = arg2
    #     def run(self):
    #         MyThread.count += 1
    #         time.sleep(1)
    #         print('%s,%s,%s,%s'%(self.arg1,self.arg2,self.name,os.getpid()))
    #
    # for i in range(10):
    #     t = MyThread(i,i*'*')
    #     t.start()
    # print(t.count)
    

      1.4,Thread类的其他方法

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

     

    import time
    import threading
    def func(i):
        time.sleep(0.5)
    #     print(i,threading.currentThread())
        print(i,threading.currentThread().name,threading.currentThread().ident)
        # ident 线程id
    for i in range(10):
        t = threading.Thread(target=func,args=(i,))
        t.start()
    
    # print(len(threading.enumerate()))  # 返回正在运行着的线程列表,返回11个(主线程也是)
    print(threading.activeCount())    # 当前还存活着的线程。
    

    2,多线程与多进程

    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello',os.getpid())
    
    if __name__ == '__main__':
        #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
        t1=Thread(target=work)
        t2=Thread(target=work)
        t1.start()
        t2.start()
        print('主线程/主进程pid',os.getpid())
    
        #part2:开多个进程,每个进程都有不同的pid
        p1=Process(target=work)
        p2=Process(target=work)
        p1.start()
        p2.start()
        print('主线程/主进程pid',os.getpid())
    pid的比较
    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello')
    
    if __name__ == '__main__':
        #在主进程下开启线程
        t=Thread(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        hello
        主线程/主进程
        '''
    
        #在主进程下开启子进程
        t=Process(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        主线程/主进程
        hello
        '''
    开启效率的较量
    from  threading import Thread
    from multiprocessing import Process
    import os
    def work():
        global n
        n=0
    
    if __name__ == '__main__':
        # n=100
        # p=Process(target=work)
        # p.start()
        # p.join()
        # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
    
    
        n=1
        t=Thread(target=work)
        t.start()
        t.join()
        print('',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
    同一进程内的线程共享该进程的数据?
    内存数据的共享问题

     3,实例

    import socket
    from threading import Thread
    def func(conn):
        conn.send(b'hello')
        ret = conn.recv(1024)
        print(ret)
        conn.close()
    
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        Thread(target=func,args=(conn,)).start()
    sk.close()
    sever端
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',8080))
    
    ret = sk.recv(1024)
    print(ret)
    msg = input('>>>')
    sk.send(msg.encode('utf-8'))
    
    sk.close()
    client端

     

    守护线程程

      1) 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
      2) 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而主进程必须保证非守护线程都运行完毕后才能结束。

    1,创建守护线程

    import time
    from threading import Thread
    def func():
        print('开始执行子线程')
        time.sleep(3)
        print('子线程执行完毕')
    
    t = Thread(target=func)
    t.setDaemon(True)   # 必须在t.start()之前设置
    # 线程设置是个方法 # 进程设置守护进程 是一个属性 daemon = True t.start() # t是个守护线程 t2 = Thread(target=func) t2.start() # t2是个线程 # t2.join() # 等待t2结束

      1.1,上面代码结尾1

    t2 = Thread(target=func)
    t2.start()  
    # 主进程代码执行完毕,守护进程就结束了,主进程没有结束等待 t2 线程结束。
    # 主线程还没结束 ,等待t2继续执行,t2执行完毕, 主线程结束(也意味着主进程结束)

    1.2,上面代码结尾2

    t2 = Thread(target=func)
    t2.start()
    t2.join()    # 等待t2结束 执行完这句话代码才执行完毕

    # 主线程中没有运行的线程了,守护线程结束,该代码的主线程结束。
    # 且主进程没有也没有代码了,守护进程结束,该代码的主进程结束。

    线程——锁

    1,锁与GIL

      1)虽然有线程锁,即cpu只给每个线程一个固定的运行时间,该时间内即使没运行完,cpu也会换下一个线程处理。依次轮换。
      2)还是存在安全问题,如,所有线程对一个数据进行修改,其中一个线程拿到了要修改的数据,并存入自己的线程,但此时正好cpu给的时间到了,该线程停止,此时,原数据并没有被修改成处理后的数据,下一个线程还是取这个数据,存入自己的线程,然后时间又到了,cpu又处理下一个线程,当再次轮到未处理完的这些线程时,线程继续之前未进行完的处理,那么问题来了,此时,每个线程事实上都处理了一变,例如原数据是一百,每个线程都是取数据减一,然而这些线程拿到的数据都是一百,处理后都是99,而原本的意图是,数据每次减一。

      1.1,存在上面所说的问题的代码

        # 下面的程序,有线程锁就可以满足,防止数据不安全
        # 但是,加上延迟后(模拟处理时间长)后出现数据不安全。

    import time
    from threading import Thread
    
    def func():
        global n
    #     n -= 1   	
        temp = n   # 从进程中获取n
        time.sleep(0.01)
        n = temp-1 # 得到结果,再存储回进程
    
    n = 100
    t_lst = []
    for i in range(100):
        t = Thread(target=func)
        t.start()
        t_lst.append(t)
    [t.join() for t in t_lst]
    print(n)
    

      1.2,解决上面的问题,对数据上锁。

    import time
    from threading import Thread
    from threading import Lock
    def func():
        global n
    #     time.sleep(2)  # 用join,此处线程
        lock.acquire()
        temp = n  
        time.sleep(0.01)
        n = temp-1 # 得到结果,再存储回进程
        lock.release()
    
    n = 100
    lock = Lock()
    t_lst = []
    for i in range(100):
        t = Thread(target=func)
        t.start()
        t_lst.append(t)
    [t.join() for t in t_lst]
    print(n)
    

      # GIL 不是锁数据 而是锁线程
      # 在多线程中 特殊情况 仍然要加锁 对数据
      # 会变慢,因为加锁后每个线程依次sleep,不是一起sleep。

    2,死锁

      1)科学家吃面 : 现有四个人吃一份面,吃面需满足两个条件,1、拿到筷子(锁住),2、拿到面(锁住)。
      2)死锁现象:定义了两种吃法,1、先拿面后拿筷子,2、先拿筷子后拿面。 会出现一个人拿到了筷子,一个人拿到面,然后都再等着拿另一个,然后谁也吃不成。

      2.1,死锁现象

    import time
    from threading import Lock
    from threading import Thread
    kz = Lock()
    m = Lock()
    
    def eat(name):
        kz.acquire()   # 拿钥匙
        print('%s拿到筷子了'%name)
        m.acquire()
        print('%s拿到面了'%name)
        print('%s吃面'%name)
        m.release()
        kz.release()
    
    def eat2(name):
        m.acquire()  
        print('%s拿到面了' % name)
        time.sleep(1)
        kz.acquire()
        print('%s拿到筷子了' % name)
        print('%s吃面' % name)
        kz.release()
        m.release()
    
    Thread(target=eat,args=('哪吒',)).start()
    Thread(target=eat2,args=('egon',)).start()
    Thread(target=eat,args=('苑昊',)).start()
    Thread(target=eat2,args=('金老板',)).start()
    死锁

     3,递归锁

      3.1,解决死锁: 用RLock

        递归锁:再一个线程里,同一把锁可以锁多层。

    from threading import RLock   # 递归锁   
    from threading import Lock    # 互斥锁
    lock = RLock()  # 递归锁
    lock.acquire()  # 开一层锁
    lock.acquire()  # 再开一层锁
    lock.acquire()   # 再开一层锁
    print(123)     # 被锁的三层
    lock.release()  # 锁上最里层
    lock.release()  # 再锁一层
    lock.release()  # 锁最外层
    

      3.2,为什么不用互斥锁这样写?

    from threading import Lock    # 互斥锁
    kz.release()  # 互斥锁
    kz.acquire()  # 开锁
    kz.acquire()  # 虽然是同一把锁,但还是等着拿钥匙。
    print(123)
    kz.release()
    kz.release()
    

      3.3,选  锁 或者 递归锁 ?

        在多线程并发的情况下,同一个线程中 如果出现多次acquire 就可能产生死锁线程现象,用递归锁就能避免

    线程——信号量

    1,信号量

    import time
    import random
    from threading import Thread
    from threading import Semaphore
    
    def func(n,sem):
        sem.acquire()
        print('thread -%s start'%n)
        time.sleep(random.random())
        print('thread -%s done' % n)
        sem.release()
    
    sem = Semaphore(5)   # 一把锁有5把钥匙
    for i in range(20):
        Thread(target=func,args=(i,sem)).start()
    

    2,信号量 和 线程池 有什么区别?

      相同点 :
             在信号量acquire之后,和线程池一样 同时在执行的只能有n个
      不同点 :
             开的线程数不一样 线程池来说 一共就只开5个线程 信号量有几个任务就开几个线程
    3,对有信号量限制的程序来说 可以同时执行很多线程么?
      实际上 信号量并不影响线程或者进程的并发,只是在加锁的阶段进行流量限制。即池中最多开五个线程,而信号量是开所有的设定的而线程,即在执行有加锁函数线程时,没有加锁的部分所有线程都会执行,没有限制,只是在执行到加锁部分时,才进行流量限制。

    # 上面的代码
    def func(n,sem): # 所有线程都会开启 print('123') # 未加锁部分都会无限制执行,没有进房间前 print('123') sem.acquire() # 加锁部分进行流量限制 ,限制进入房间的数量。 print('thread -%s start'%n) time.sleep(random.random()) print('thread -%s done' % n) sem.release()

    线程——事件

    # 设置事件内部会有一个flag标志,开始时flag = False
    # wait() 时 flag = False 阻塞,flag = True 非阻塞
    # set()   将 flag = True
    # clear() 将 flag = False

    1,例子:连接MySQL数据库

      1)我连接三次数据库,每0.5秒连接一次
      2)创建一个事件 来标志数据库的连接情况,如果连接成功,就显示成功,否则 就报错 主动抛异常TimeoutError

    import time
    import random
    from threading import Event
    from threading import Thread
    def conn_mysql():  # 连接数据库
        count = 1
        while not e.is_set():  # 当事件的flag为False时才执行循环内的语句
            if count>3:
                raise TimeoutError
            print('尝试连接第%s次'%count)
            count += 1
            e.wait(0.5)  # 一直阻塞变成了只阻塞0.5
        print('连接成功')  # 收到check_conn函数内的set指令,让flag变为True跳出while循环,执行本句代码
    
    def check_conn():
        '''
        检测数据库服务器的连接是否正常
        '''
        time.sleep(random.randint(1,2))  # 模拟连接检测的时间
        e.set() # 告诉事件的标志数据库可以连接
    
    e = Event()
    check = Thread(target=check_conn)
    check.start()
    conn = Thread(target=conn_mysql)
    conn.start()
    

    定时器  Timer

    from threading import Timer
    
    def hello():
            print("hello, world")
    
    while True:    # 每隔一段时间要开启一个线程
        t = Timer(10, hello)   # 定时开启一个线程,执行一个任务
                          # 定时 : 多久之后 单位是s
                          # 要执行的任务 :函数名
        t.start()
    

      也可以用下面的方式:

    def hello(): 
        while True:      # 这样做这个线程一直在
            time.sleep(10)
            print("hello, world")
    

      用哪个?看sleep的时间

      # sleep的时间短 就在线程内while True
      # sleep的时间长 就在主线程while True

    条件

    import threading
    def run(n):
        con.acquire()
        con.wait()       # 等着信号
        print("run the thread: %s" % n)
        con.release()
    
    if __name__ == '__main__':
        con = threading.Condition()   # 条件  = 锁 + wait的功能
        for i in range(10):
            t = threading.Thread(target=run, args=(i,))
            t.start()
    
        while True:
            inp = input('>>>')
            if inp == 'q':
                break
            con.acquire()        # condition中的锁 是 递归锁
            if inp == 'all':
                con.notify_all()  # 全部放行
            else:
                con.notify(int(inp))   # 传递信号 notify(1) --> 可以放行一个线程  # 设置放行几个线程
            con.release()
    

    队列

    1,队列

    # import queue
    # q = queue.Queue() # 队列 线程安全的 # q.get() # q.put() # q.qsize()

    2,后进先出

    # import queue
    # lfq = queue.LifoQueue() # 后进先出 :栈 # lfq.put(1) # lfq.put(2) # lfq.put(3) # lfq.put(4) # print(lfq.get()) # print(lfq.get()) # print(lfq.get()) # print(lfq.get())

    3,优先级

    import queue
    pq = queue.PriorityQueue()  
    # 有优先级的队列 # 值越小越优先,值相同就asc码小的先出
    pq.put((1,'z'))
    pq.put((1,'b'))
    pq.put((15,'c'))
    pq.put((2,'d'))
    #
    print(pq.get())
    print(pq.get())
    

    线程池

    # from concurrent import futures
    # futures.ThreadPoolExecutor    # 开线程池
    # futures.ProcessPoolExecutor   # 开进程池
    # 进程池:cpu个数 + 1(默认数量)
    # 线程池:cpu个数 * 5 (默然数量)

    1,创建线程池

    import time
    import random
    from concurrent import futures
    
    def funcname(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    thread_pool = futures.ThreadPoolExecutor(5)   # 创建有5个线程的线程池
    f_lst = []
    for i in range(10):
        f = thread_pool.submit(funcname,i)   # submit 合并了创建线程对象和start的功能
    #     print(f.result())     # 直接在这打印就变成同步的了
        f_lst.append(f)       # 通过将线程对象放入列表,还保持异步
    [print(i.result()) for i in f_lst]  # 一定是按顺序输出结果,遇到没执行玩的线程对象,f.result会阻塞。
    创建线程池

    2,就想等所有线程执行完了再输出结果

    import time
    import random
    from concurrent import futures
    
    def funcname(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    thread_pool = futures.ThreadPoolExecutor(5)   # 创建有5个线程的线程池
    f_lst = []
    for i in range(10):
        f = thread_pool.submit(funcname,i)   # submit 合并了创建线程对象和start的功能
    #     print(f.result())     # 直接在这打印就变成同步的了
        f_lst.append(f)       # 通过将线程对象放入列表,还保持异步
    thread_pool.shutdown()  # 有close() join()的作用  # 没有join方法
    [print(i.result()) for i in f_lst]  
    View Code

      2.1,或者用map

        # map,天生异步,接收可迭代对象的数据,不支持返回值

    thread_pool = futures.ThreadPoolExecutor(5)
    thread_pool.map(funcname,range(10))  
    View Code

    回调函数

    1,add_done_callback(回调函数的名字)

    import time
    import random
    from concurrent import futures
    
    def funcname(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    def call(args):
        print(args.result())
    
    thread_pool = futures.ThreadPoolExecutor(5)
    thread_pool.submit(funcname,1).add_done_callback(call)
    

    ****************************************************************************

    Python标准模块--concurrent.futures 

    https://docs.python.org/dev/library/concurrent.futures.html

    #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)
    回调函数
    
    # done()
    判断某一个线程是否完成
    
    # cancle()
    取消某个任务
    
    #介绍
    The ProcessPoolExecutor class is an Executor subclass that uses a pool of processes to execute calls asynchronously. ProcessPoolExecutor uses the multiprocessing module, which allows it to side-step the Global Interpreter Lock but also means that only picklable objects can be executed and returned.
    
    class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None)
    An Executor subclass that executes calls asynchronously using a pool of at most max_workers processes. If max_workers is None or not given, it will default to the number of processors on the machine. If max_workers is lower or equal to 0, then a ValueError will be raised.
    
    
    #用法
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    import os,time,random
    def task(n):
        print('%s is runing' %os.getpid())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ProcessPoolExecutor(max_workers=3)
    
        futures=[]
        for i in range(11):
            future=executor.submit(task,i)
            futures.append(future)
        executor.shutdown(True)
        print('+++>')
        for future in futures:
            print(future.result())
    ProcessPoolExecutor
    #介绍
    ThreadPoolExecutor is an Executor subclass that uses a pool of threads to execute calls asynchronously.
    class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='')
    An Executor subclass that uses a pool of at most max_workers threads to execute calls asynchronously.
    
    Changed in version 3.5: If max_workers is None or not given, it will default to the number of processors on the machine, multiplied by 5, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.
    
    New in version 3.6: The thread_name_prefix argument was added to allow users to control the threading.Thread names for worker threads created by the pool for easier debugging.
    
    #用法
    与ProcessPoolExecutor相同
    ThreadPoolExecutor
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    import os,time,random
    def task(n):
        print('%s is runing' %os.getpid())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ThreadPoolExecutor(max_workers=3)
    
        # for i in range(11):
        #     future=executor.submit(task,i)
    
        executor.map(task,range(1,12)) #map取代了for+submit
    map的用法
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    from multiprocessing import Pool
    import requests
    import json
    import os
    
    def get_page(url):
        print('<进程%s> get %s' %(os.getpid(),url))
        respone=requests.get(url)
        if respone.status_code == 200:
            return {'url':url,'text':respone.text}
    
    def parse_page(res):
        res=res.result()
        print('<进程%s> parse %s' %(os.getpid(),res['url']))
        parse_res='url:<%s> size:[%s]
    ' %(res['url'],len(res['text']))
        with open('db.txt','a') as f:
            f.write(parse_res)
    
    
    if __name__ == '__main__':
        urls=[
            'https://www.baidu.com',
            'https://www.python.org',
            'https://www.openstack.org',
            'https://help.github.com/',
            'http://www.sina.com.cn/'
        ]
    
        # p=Pool(3)
        # for url in urls:
        #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
        # p.close()
        # p.join()
    
        p=ProcessPoolExecutor(3)
        for url in urls:
            p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
    回调函数
  • 相关阅读:
    安装完MySQL数据库设置密码
    pom.xml
    性能测试更像一次科学实验
    gitlab git
    postman
    python3 session cookie
    自动化测试的概念及工具
    项目启动加载配置,以及IP黑名单,使用CommandLineRunner和ApplicationRunner来实现(一般用在网关进行拦截黑名单)
    使用JWT登录生成token
    国际化的实现i18n--错误码国际化以及在springboot项目中使用
  • 原文地址:https://www.cnblogs.com/eternity-twinkle/p/10727554.html
Copyright © 2020-2023  润新知