python9-多线程

1,概念

  • 线程:
    • 能被操作系统调度(被CPU执行)的最小单位。(进程负责圈资源,线程负责执行)
    • 同一个进程中的多个线程能够同时被多个CPU执行。(线程也可以利用多核)
    • 同一个进程的多个线程是数据共享的。(必然存在数据不安全)(进程与线程的区别之一:进程是数据隔离的,线程是数据共享的)
    • 线程的开启、关闭、切换的时间开销相比与进程要小的多。(一般情况下开启的进程数不会超过CPU个数的两倍,而开启的线程数可以很多)(因此绝大部分的并发都是由线程完成的)
    • CPython中的多线程:

2,threading模块

  • 开启线程、传参、join、获取当前线程的对象

  • Thread, targert, args, start, join, ident(线程id),current_thread(获取当前线程的对象),enumerate(列表,存储了所有活着的线程对象,注意包括主线程),active_count(就是计算enumerate结果的长度,也就是所有活着的线程的数目),daemon(守护线程:守护线程随主线程的结束而结束,主线程等所有子线程结束后结束)(其他子线程结束——》主线程结束——》进程结束——》整个进程的所有资源被回收——》守护线程结束被回收(守护线程的结束在进程终止之后)

    • 守护进程与守护线程的区别
      • 守护进程会随着主进程的代码结束而结束,如果主进程代码结束以后还有其他的子进程运行,守护进程不守护。
      • 守护线程随着主线程/进程的结束而结束,如果主线程结束之后还有其他子线程在运行(此时进程也不会结束),守护线程也守护。
    • 为什么有这种区别?
      • 父进程负责回收所有开的子进程的资源;但主线程没有必要回收子线程的资源(资源回收和线程没有关系);主线程的结束意味着进程的结束,进程的结束进程所有的资源回收
  • 线程没有terminate(线程不能从外部关闭,只能自己执行完后关闭)

  • 以对象方式开启线程,线程的启动使用start(start是开启线程,而run只是执行方法,必须要先开启线程才能在线程中执行run方法)

    '''例子一'''
    import time
    from threading import Thread
    
    def func(i):
        print(f'start {i}')
        time.sleep(2)
        print(f'end {i}')
    
    threadList = []
    for i in range(10):
        th = Thread(target=func, args=(i, ))
        th.start()		#开启
        threadList.append(th)
    for thread in threadList:
        thread.join()	#等全部结束了再打印end
    print('\033[31mend\033[0m')
    
    '''例子二,对比进程和线程的开启速度,线程是真的快(时间开销差距大概在100倍左右)'''
    import time
    from threading import Thread
    from multiprocessing import Process
    
    def func(i):
        print(f'start {i}')
        time.sleep(2)
        print(f'end {i}')
    
    if __name__ == '__main__':
        for i in range(10):
            th = Thread(target=func, args=(i, ))
            th.start()	#开启
            # p = Process(target=func, args=(i, ))
            # p.start()
        print('\033[31mend\033[0m')	#开启完所有线程就打印end
    '''进程几乎都是先打印代码最后的end,再打印start{i};而线程则是先打印start{i},再打印代码最后的end;这说明了两者在开启速度上的差别'''    
    
    '''例子三,线程也有id,此id与进程的pid无关'''
    import time
    import os
    from threading import Thread
    
    def func(i):
        print(f'start {i}')
        time.sleep(2)
        print(f'end {i}')
    
    if __name__ == '__main__':
        for i in range(10):
            th = Thread(target=func, args=(i, ))
            th.start()
            print(th.ident, os.getpid())	#打印线程id,以及进程pid
        print('\033[31mend\033[0m')
    
    '''例子四,在函数中获取线程的id'''
    '''方法一:current_thread,特点:在哪个线程中执行,current_thread所获取的就是哪个线程所对应的对象'''
    import time
    import os
    from threading import Thread, current_thread
    
    def func(i):
        print(f'start {i}', current_thread(), current_thread().ident)
        time.sleep(2)
        print(f'end {i}')
    
    if __name__ == '__main__':
        for i in range(10):
            th = Thread(target=func, args=(i, ))
            th.start()
            print(th.ident)		#打印线程id
        print('\033[31mend\033[0m')
    
    '''例子五,enumerate 与 active_count'''
    import time
    import os
    from threading import Thread, current_thread, enumerate, active_count
    
    def func(i):
        print(f'start {i}')
        time.sleep(2)
        print(f'end {i}')
    
    if __name__ == '__main__':
        for i in range(10):
            th = Thread(target=func, args=(i, ))
            th.start()
        print(enumerate())		#列出所有的线程
        print(len(enumerate()), active_count())
        print('\033[31mend\033[0m')
    
    '''例子六,面向对象的方式开启线程'''
    import time
    from threading import Thread, enumerate
    
    class myThread(Thread):
        def __init__(self, args):
            super(myThread, self).__init__()
            self.args = args
        def run(self):
            print(f'start {self.args}')
            time.sleep(2)
            print(f'end {self.args}')
    
    if __name__ == '__main__':
        for i in range(10):
            th = myThread((i, 2))
            th.start()		#注意这里是start(start是开启线程,而run只是执行方法,必须要先开启线程才能在线程中执行run方法)
        print(enumerate())
        print(len(enumerate()))
        print('\033[31mend\033[0m')
    
    '''例子七,线程之间的数据是共享的'''
    from threading import Thread
    n = 100
    
    def func():
        global n
        n -= 1
    
    tList = []
    for i in range(100):
        th = Thread(target=func)
        th.start()
        tList.append(th)
    for t in tList:
        t.join()
    print(n)	#最后的结果是0,s
    
    '''例子八,守护线程'''
    '''
    =============================
    主线程会等待子线程的结束再结束(主线程结束主进程就会结束,而之后回收资源)
    =============================
    '''
    import time
    from threading import Thread
    
    def son():
        while True:
            print('in son')
            time.sleep(1)
    
    Thread(target=son).start()
    
    '''
    =============================
    1,线程开启的速度很快,从flagA到flagB的打印几乎瞬间完成
    2,守护线程守护的是主线程,随主线程的结束而结束
    =============================
    '''
    import time
    from threading import Thread
    
    def son():
        while True:
            print('in son')
            time.sleep(1)
    
    print('flagA')
    th = Thread(target=son)
    th.daemon = True
    th.start()
    print('flagB')
    

3,线程锁(互斥锁)

  • +=, -=, *=, /=, while, if 都是数据不安全的;列表中或者字典中的方法操作全局变量时是数据安全的。

  • 解决数据不安全的问题就是加锁

  • 如何避免数据不安全:不要操作全局变量、不要在类里操作静态变量(单例模式跑不掉)

    from threading import Thread, Lock
    n = 0
    def increase(lock):
        for i in range(2200000):
            global n
            with lock:
                n += 1
    def reduce(lock):
        for i in range(2200000):
            global n
            with lock:
                n -= 1
    
    lock = Lock()
    t1 = Thread(target=increase, args=(lock, ))
    t2 = Thread(target=reduce, args=(lock, ))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(n)
    
    '''单例模式'''
    '''=====================================不安全版本====================================='''
    import time
    
    class A:
        __instance = None
        def __new__(cls, *args, **kwargs):
            if not cls.__instance:
                time.sleep(0.0000001)	#这里强制CPU轮转,单例模式出现了多个实例(此处只要有CPU轮转,都可能会有多个实例)
                cls.__instance = super().__new__(cls)
            return cls.__instance
    
    def func():
        a = A()
        print(a)
    
    from threading import Thread
    for i in range(10):
        Thread(target=func).start()
        
    '''=====================================安全版本====================================='''
    import time
    from threading import Thread, Lock
    
    class A:
        __instance = None
        __lock = Lock()
        def __new__(cls, *args, **kwargs):
            with cls.__lock:
                if not cls.__instance:
                    time.sleep(0.0000001)
                    cls.__instance = super().__new__(cls)
                return cls.__instance
    
    def func():
        a = A()
        print(a)
    
    for i in range(10):
        Thread(target=func).start()
    

4,递归锁和死锁现象

  • 死锁

    • 产生原因:最主要的原因是有多把锁,并且在多个线程中进行交叉使用;死锁现象和锁是互斥锁还是递归锁无关;但是如果可以只有一把锁就能够解决死锁问题(比如将所有的互斥锁都改为一把递归锁)。

    • from threading import Thread, Lock
      import time
      
      noodleLock = Lock()
      forkLock = Lock()
      
      def eat1(name):
          noodleLock.acquire()
          print(name, '抢到面了')
          forkLock.acquire()
          print(name, '抢到叉子了')
          print(name, '吃面')
          time.sleep(0.1)
          forkLock.release()
          print(name, '放下叉子')
          noodleLock.release()
          print(name, '放下面')
      
      def eat2(name):
          forkLock.acquire()
          print(name, '抢到叉子了')
          noodleLock.acquire()
          print(name, '抢到面了')
          print(name, '吃面')
          time.sleep(0.1)
          forkLock.release()
          print(name, '放下叉子')
          noodleLock.release()
          print(name, '放下面')
      
      Thread(target=eat1, args=('a')).start()
      Thread(target=eat2, args=('b')).start()
      Thread(target=eat1, args=('c')).start()
      Thread(target=eat2, args=('d')).start()
      
  • 递归锁

    • 可以多次acquire;acquire几次就要release几次

    • 递归锁的效率比互斥锁效率低

    • from threading import Thread, RLock
      import time
      
      noodleLock = forkLock = RLock()
      
      def eat1(name):
          noodleLock.acquire()
          print(name, '抢到面了')
          forkLock.acquire()
          print(name, '抢到叉子了')
          print(name, '吃面')
          time.sleep(0.1)
          forkLock.release()
          print(name, '放下叉子')
          noodleLock.release()
          print(name, '放下面')
      
      def eat2(name):
          forkLock.acquire()
          print(name, '抢到叉子了')
          noodleLock.acquire()
          print(name, '抢到面了')
          print(name, '吃面')
          time.sleep(0.1)
          forkLock.release()
          print(name, '放下叉子')
          noodleLock.release()
          print(name, '放下面')
      
      Thread(target=eat1, args=('a')).start()
      Thread(target=eat2, args=('b')).start()
      Thread(target=eat1, args=('c')).start()
      Thread(target=eat2, args=('d')).start()
      

5,队列(queue:Queue/LifoQueue/PriorityQueue)

  • 线程之间数据安全的容器

  • 用法

    q = queue.Queue(n)	#FIFO,先进先出(队列);用于任务处理
    q.get()
    q.get_nowait()
    q.put()
    q.put_nowait()	#会丢失数据
    
    try:
        q.get_nowait()
    except queue.Empty:
        pass
    
    q = queue.LifoQueue	#LIFO,后进先出(栈);用于算法
    '''用法与队列类似'''
    
    priq = queue.PriorityQueue	#优先级队列;用于服务、运维等
    priq.put((x, y))			#根据x的ascii码排序
    priq.get()
    

6,池 concurrent.futures

  • 线程池的开启过程:

    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    tp = ThreadPoolExecutor(20)    #启线程池,线程的数目一般是CPU的核心数*5
    ret = tp.submit(func)          #向线程池中提交任务
    ret.result()				   #获取结果
    
    '''=============回调函数============='''
    ret.add_done_callback(lambda x: print(f'\033[33m{x}{x.result()}\033[0m'))
    
  • 概念:

    • 没有池——》有多少任务就开多少个进程/线程
    • 有了池——》程序开始的时候还没提交任务就先创建几个进程/线程放在一个池子里,这就是池
  • 为什么要用池?

    • 如果先开好进程/线程,有任务之后就可以直接使用池中的进程/线程;
    • 开好的进程/线程会一直存在在池中,可以被多个任务反复使用,极大的减少了开启、关闭、调度线程/进程的时间开销;
    • 池中的进程/线程个数意味着操作系统需要调度的任务个数,控制池中的进程/线程个数有利于提高操作系统的效率,减轻操作系统的负担。
  • 模块发展史

    • threading 线程模块,没有池
    • multiprocessing 仿照threading写的进程模块,有进程池
    • concurrent.futures 进程池和线程池都有,能够用相似的方式开启,使用。
  • 用例:

    '''1,启动池'''
    '''====================================线程池===================================='''
    import time
    import random
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func():
        print(current_thread().ident)   #观察当前线程的编号
        # time.sleep(1)                 #强制轮转
        # time.sleep(random.randint(1, 4))
    
    tp = ThreadPoolExecutor(20)    #启线程池,线程的数目一般是CPU的核心数*5
    
    for i in range(40):
        tp.submit(func)            #向线程池中提交任务
    '''====================================进程池===================================='''
    import time
    import random
    import os
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(a, b):
        print(os.getpid(), current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        time.sleep(1)                 #强制轮转
        # time.sleep(random.randint(1, 4))
        print(os.getpid(), current_thread().ident, 'end:', a, b)  # 观察当前线程的编号
    
    tp = ProcessPoolExecutor(5)    #启进程池,进程的数目一般是CPU的核心数+1
    
    if __name__ == '__main__':
        for i in range(40):
            # tp.submit(func, i, i+1)            #向线程池中提交任务,传参
            tp.submit(func, a=i, b=i+1)        #向线程池中提交任务,传参
    
    '''2,传参'''
    import time
    import random
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(a, b):
        print(current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        # time.sleep(1)                 #强制轮转
        # time.sleep(random.randint(1, 4))
    
    tp = ThreadPoolExecutor(20)    #启线程池,线程的数目一般是CPU的核心数*5
    
    for i in range(40):
        # tp.submit(func, i, i+1)            #向线程池中提交任务,传参
        tp.submit(func, a=i, b=i+1)        #向线程池中提交任务,传参
    
    '''3,获取任务结果'''
    '''====================================同步===================================='''
    import time
    import random
    import os
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(a, b):
        print(os.getpid(), current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        time.sleep(1)                 #强制轮转
        # time.sleep(random.randint(1, 4))
        print(os.getpid(), current_thread().ident, 'end:', a, b)  # 观察当前线程的编号
        return a*b
    
    tp = ProcessPoolExecutor(5)    #启进程池,进程的数目一般是CPU的核心数+1
    
    if __name__ == '__main__':
        for i in range(40):
            ret = tp.submit(func, i, i+1)            #向线程池中提交任务,传参
            print(f'\033[33m{ret}\033[0m')           #获取结果,一个future对象
            print(f'\033[33m{ret.result()}\033[0m')  #这里print阻塞必须要等函数的执行结果,变成同步了
    '''====================================异步===================================='''
    import time
    import random
    import os
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(a, b):
        print(os.getpid(), current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        # time.sleep(1)                 #强制轮转
        time.sleep(random.randint(1, 4))
        print(os.getpid(), current_thread().ident, 'end:', a, b)  # 观察当前线程的编号
        return a*b
    
    tp = ProcessPoolExecutor(5)    #启进程池,进程的数目一般是CPU的核心数+1
    
    if __name__ == '__main__':
        resList = []
        for i in range(40):
            ret = tp.submit(func, i, i+1)            #向线程池中提交任务,传参
            resList.append(ret)
        for res in resList:                          #列表内部是同步的,如果其中一个res没有返回会一直等只到获取结果;并且获取的结果与放入的顺序是一致的
            print(f'\033[33m{res.result()}\033[0m')
    
    '''4,map'''
    import time
    import random
    import os
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(dataIn):
        a, b = dataIn
        print(os.getpid(), current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        # time.sleep(1)                 #强制轮转
        time.sleep(random.randint(1, 4))
        print(os.getpid(), current_thread().ident, 'end:', a, b)  # 观察当前线程的编号
        return a*b
    
    tp = ProcessPoolExecutor(5)    #启进程池,进程的数目一般是CPU的核心数+1
    
    if __name__ == '__main__':
        ret = tp.map(func, ((i, i+1) for i in range(20)))   #只适合传递简单的参数并且必须是可迭代对象
        for res in ret:
            print(f'\033[33m{res}\033[0m')
    
    '''5,回调函数add_done_callback:异步阻塞'''
    import time
    import random
    import os
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    from threading import current_thread
    
    def func(dataIn):
        a, b = dataIn
        print(current_thread().ident, 'start:', a,b)   #观察当前线程的编号
        # time.sleep(1)                 #强制轮转
        time.sleep(random.randint(1, 4))
        print(current_thread().ident, 'end:', a, b)  # 观察当前线程的编号
        return a*b
    
    tp = ThreadPoolExecutor(5)    #启进程池,进程的数目一般是CPU的核心数+1
    
    if __name__ == '__main__':
        future_l = []
        for i in range(20):
            ret = tp.submit(func, dataIn=(i, i+1))
            ret.add_done_callback(lambda x: print(f'\033[33m{x}{x.result()}\033[0m'))#在ret执行结束的瞬间就会将		ret的结果传给通过add_done_callback绑定的func,并执行func   
            '''add_done_callback回调函数,异步阻塞。给ret对象绑定一个回调函数,等待ret对应的任务有了结果之后立即调用
            传入的函数就可以对结果进行立即处理,而不用按照顺序接受ret在进行处理。这样一来:计算func和获取结果的过程都是异步			的。'''
    
    '''6,使用queue模拟回调函数'''
    import queue
    import time
    import random
    from threading import Thread
    
    def func(q,i):
        print('start', i)
        time.sleep(random.randint(1, 10))
        print('end', i)
        q.put(i*(i+1))  #return i*(i+1)
    
    q = queue.Queue()
    for i in range(20):
        Thread(target=func, args=(q, i)).start()
    for i in range(20):
        Thread(target=lambda x: print(x.get()), args=(q,)).start()
    
posted @ 2021-07-08 09:31  tensor_zhang  阅读(70)  评论(0编辑  收藏  举报