• 线程锁&&信号量&&GIL&&线程定时器&&进程池与线程池&&协程


    一、线程锁(同步锁)(线程的互斥锁)

    1.1线程锁

    
    from threading import Thread,Lock
    x = 0
    mutex = Lock()
    def task():
        global x
        for i in range(200000):
            x = x+1
            """
            由于没有进程锁,程序切换比较快,例如:
            	t1 的x 刚拿到0   x = 0+1  还没进行运算,保存了状态,就被cpu切换
            	t2 的 x 拿到0   进行了运算  x = 1
            	又切换到t1,x = 0+1    x = 1
            	产生数据安全的问题,所以结果就会随机
            """
    if __name__ == '__main__':
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        t3 = Thread(target=task)
        t1.start()
        t2.start()
        t3.start()
        t1.join()
        t2.join()
        t3.join()
        print(x)
    #################
    337566            #比较随机,正确的应该是600000
    
    
    
    
    from threading import Thread, Lock
    
    x = 0
    mutex = Lock()
    
    
    def task():
        mutex.acquire()  # 线程锁
        global x
        for i in range(200000):
            x = x + 1
        mutex.release()
    
    
    if __name__ == '__main__':
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        t3 = Thread(target=task)
        t1.start()
        t2.start()
        t3.start()
        t1.join()
        t2.join()
        t3.join()
        print(x)
    ##########################
    600000
    

    1.2死锁

    from threading import Thread,Lock
    import time
    mutex1 = Lock()
    mutex2 = Lock()
    class MyTreada(Thread):
         def run(self):
             self.task1()
             self.task2()
         def task1(self):
             mutex1.acquire()
             print(f'{self.name}抢到了锁1')
             mutex2.acquire()
             print(f'{self.name}抢到了锁2')
             mutex2.release()
             print(f'{self.name}释放了锁1')
             mutex1.release()
             print(f'{self.name}释放了锁2')
         def task2(self):
             mutex2.acquire()
             print(f'{self.name}抢到了锁2')
             time.sleep(1)
             mutex1.acquire()
             print(f'{self.name}抢到了锁2')
             mutex2.release()
             print(f'{self.name}释放了锁2')
             mutex1.release()
             print(f'{self.name}释放了锁1')
    for i in range(3):
        t = MyTreada()
        t.start()
    ##################
    Thread-1抢到了锁1
    Thread-1抢到了锁2
    Thread-1释放了锁1
    Thread-1释放了锁2
    Thread-1抢到了锁2
    Thread-2抢到了锁1##########下面的线程被阻塞
    #############################
    """
    俩个线程
    线程1拿到了锁头2,要想往下执行需要锁头1
    线程2拿到了锁头1,想要往下执行需要锁头2
    互相拿到了彼此往下执行的必要条件(锁头),互相不放弃手里的锁头,出现阻塞
    """
    

    1.3解决死锁方法(递归锁)

    解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
    
    from threading import Thread,RLock
    """
    递归锁:在同一个线程内可以被多次acquire
    释放:内部相当于维护了一个计数器,也就是说同一个线程acquire了几次就要release()几次
    
    """
    import time
    
    mutex1 = RLock()
    mutex2 = mutex1
    
    class MyThreada(Thread):
        def run(self):
            self.task1()
            self.task2()
        def task1(self):
            mutex1.acquire()
            print(f'{self.name}抢到了锁1')
            mutex2.acquire()
            print(f'{self.name}抢到了锁2')
            mutex2.release()
            print(f'{self.name}释放了锁2')
            mutex1.release()
            print(f'{self.name}释放了锁1')
        def task2(self):
            mutex2.acquire()
            print(f'{self.name}抢到了锁2')
            time.sleep(1)
            mutex1.acquire()
            print(f'{self.name}抢到了锁1')
            mutex1.release()
            print(f'{self.name}释放了锁1')
            mutex2.release()
            print(f'{self.name}释放了锁2')
    for i in range(3):
        t = MyThreada()
        t.start()
    #############################
    Thread-1抢到了锁1
    Thread-1抢到了锁2
    Thread-1释放了锁2
    Thread-1释放了锁1
    Thread-1抢到了锁2
    Thread-1抢到了锁1
    Thread-1释放了锁1
    Thread-1释放了锁2
    Thread-2抢到了锁1
    Thread-2抢到了锁2
    Thread-2释放了锁2
    Thread-2释放了锁1
    Thread-2抢到了锁2
    Thread-2抢到了锁1
    Thread-2释放了锁1
    Thread-2释放了锁2
    Thread-3抢到了锁1
    Thread-3抢到了锁2
    Thread-3释放了锁2
    Thread-3释放了锁1
    Thread-3抢到了锁2
    Thread-3抢到了锁1
    Thread-3释放了锁1
    Thread-3释放了锁2
    
    

    二、信号量Semaphore

    同进程的一样

    实现:定制了锁的个数,也就意味着最多有几个线程可以抢到锁头

    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器+1;
    调用release() 时内置计数器-1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

    from threading import Thread,currentThread,Semaphore
    import time
    def task():
        sm.acquire()
        print(f'{currentThread().name}正在执行')
        time.sleep(3)
        sm.release()
    sm = Semaphore(5)
    for i in range(15):
        t = Thread(target=task)
        t.start()
    ####################
    Thread-1正在执行
    Thread-2正在执行
    Thread-3正在执行
    Thread-4正在执行
    Thread-5正在执行
    
    
    Thread-7正在执行
    Thread-6正在执行
    Thread-8正在执行
    Thread-9正在执行
    Thread-10正在执行
    
    
    Thread-11正在执行
    Thread-13正在执行
    Thread-12正在执行
    Thread-14正在执行
    Thread-15正在执行
    

    与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

    三、GIL

    3.1 什么是GIL

    1. 在cpython解释器中有一把GIL锁(全局解释锁),GIL锁本质是一把互斥锁。
    2. 导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势
    3. 同一个进程下的多个线程只能实现并发不能实现并行。

    3.2为什么要有GIL

    因为cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁

    导致了同一个进程下,同一时间只能由一个线程,无法利用多核优势

    分析:

    1. 我们四个任务要处理,处理方式并发
    2. 解决方案:
      1. 开启四个进程
      2. 一个进程下开启四个线程

    多进程vs多线程

    计算密集型(推荐使用多进程)

    推荐使用多进程,利用cpu的多核优势,并行的计算(cpu主要负责计算)

    下面例子,分析过程:

    1. 每个都要计算10s多线程
    2. 在同一时刻只有一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40.ns
    3. 多进程
    4. 可以并行的执行多个线程,10s+开启进程的时间
    from threading import Thread
    from multiprocessing import Process
    import time
    def workl():
        res = 0
        for i in range(10000000):
            res*=i
    if __name__ == '__main__':
        t_list = []
        start = time.time()
        for i in range(4):
            p = Process(target=workl)
            t_list.append(p)
            p.start()
        for p in t_list:
            p.join()
        end = time.time()
        print("多进程",end-start)
    ################
    多进程 1.082251787185669
    
    from threading import Thread
    from multiprocessing import Process
    import time
    def workl():
        res = 0
        for i in range(10000000):
            res*=i
    if __name__ == '__main__':
        t_list = []
        start = time.time()
        for i in range(4):
            t = Thread(target=workl)
            t_list.append(t)
            t.start()
        for t in t_list:
            t.join()
        end = time.time()
        print("多线程",end-start)
    ###############
    多进程 2.43462872505188
    
    

    IO密集型(推荐使用多线程)

    推荐使用多线程解决,大部分时间都在io,并且开启一个线程要比开启进程的速度快的多

    大部分的需求都是io密集型,因为大部分的软件都是基于网络的

    1. 4个任务每个任务90%大部分时间都在io.
    2. 每个任务io10s 0.5s
    3. 多线程
    4. 可以实现并发,每个线程io的时间不咋占用cpu, 10s + 4个任务的计算时间
    5. 多进程
    6. 可以实现并行,10s+1个任务执行的时间+开进程的时间
    from threading import Thread
    from multiprocessing import Process
    import time
    def work():
        x = 1+1
        time.sleep(5)
    if __name__ == '__main__':
        t_list = []
        start = time.time()
        for i in range(4):
            t = Thread(target=work)
            t_list.append(t)
            t.start()
        for t in t_list:
            t.join()
        end = time.time()
        print("多线程",end-start)
    ################
    多线程 5.002916097640991
    
    
    
    from multiprocessing import Process
    import time
    def work():
        x = 1+1
        time.sleep(5)
    if __name__ == '__main__':
        t_list = []
        start = time.time()
        for i in range(4):
            p = Process(target=work)
            t_list.append(p)
            p.start()
        for p in t_list:
            p.join()
        end = time.time()
        print("多进程",end-start)
    #################
    多进程 5.1951446533203125
    

    四、线程Queue()队列

    1.1 先进先出

    import queue
    q = queue.Queue()
    q.put('123')
    q.put('dd')
    print(q.get())
    print(q.get())
    ###############
    123
    dd
    

    1.2 先进后出(堆栈)

    import queue
    q = queue.LifoQueue()#堆栈,后进先出
    q.put('ocean')
    q.put('sku')
    q.put('chen')
    print(q.get())
    print(q.get())
    print(q.get())
    ##############
    chen
    sku
    ocean
    

    1.3优先级取数据

    import queue
    q = queue.PriorityQueue()
    q.put((50,'chen'))#通常元组的第一个值是int类型
    q.put((100,'sky'))
    q.put((1,'rocky'))
    print(q.get())
    print(q.get())
    print(q.get())
    ##############
    (1, 'rocky')
    (50, 'chen')
    (100, 'sky')
    
    import queue
    q = queue.PriorityQueue()
    q.put('chen')
    q.put('sky')
    q.put('rocky')
    print(q.get())
    print(q.get())
    print(q.get())
    ##################
    chen
    rocky
    sky
    
    import queue
    q = queue.PriorityQueue()
    q.put(50)
    q.put(100)
    q.put(1)
    print(q.get())
    print(q.get())
    print(q.get())
    ############
    1
    50
    100
    

    五、线程定时器

    和time.sleep()不同,线程定时器不会阻碍下面代码的执行,而time.sleep()会阻碍。

    from threading import Timer
    import time
    def task():
        print('线程执行了')
        time.sleep(2)
        print('线程结束了')
    t = Timer(4,task)###元组第一个数是多少秒开启线程
    t.start()
    print("不会阻碍线程的执行")
    ########################
    不会阻碍线程的执行
    线程执行了
    线程结束了
    

    六、多线程并发socket服务端

    1. 服务端

      import socket 
      from threading import Thread
      def talk(conn):
          while True:
              try:
                  msg = conn.recv(1024)
                  if len(msg)==0:break
                  conn.send(msg.upper())
              except Exception:
                  print('客户点关闭连接')
                  break
      def server_demo():
          server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
          server.bind(("127.0.0.1",8008))
          server.listen(5)
          
          while True:
              conn,addr = server.accept()
              t = Thread(target=talk,args=(conn,))
              t.start()
      if __name__ == '__main__':
          server_demo()
      
    2. 客户端

      import socket
      from threading import Thread,currentThread
      def client_dome():
          client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
          client.connect(('127.0.0.1',8008))
          while True:
              msg = f"{currentThread().name}"
              if len(msg)==0:continue
              client.send(msg.encode('utf8'))
              feedback = client.recv(1024)
              print(feedback.decode('utf8'))
          client.close()
      if __name__ == '__main__':
          for i in range(20):
              t = Thread(target=client_dome)
              t.start()
      

    七、进程池和线程池

    4.1进程池和线程池的意义

    1. 进程池和线程池

      1. 进程池和线程池的功能是限制进程数或者线程数。
    2. 什么时候限制?

      当并发的任务数量远远大于计算机所能承受的范围的时候,即无法一次性开启过多的任务数量,就应该考虑去限制进程数或者线程数,从而保证服务器不会崩溃。(荡机)

    4.2 同步异步

    4.2.1同步

    同步:提交一个任务,必须等待任务执行完毕(或者最后拿到返回值),才能执行下一行代码

    4.2.1.1进程池

    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    from threading import currentThread
    from multiprocessing import current_process
    import time
    
    def task(i):
        print(f'{current_process().name}在执行任务{i}')
        time.sleep(1)
        return i**2
    if __name__ == '__main__':
        pool = ProcessPoolExecutor(4)#池子里有四个进程
        fu_list = []
        for i in range(20):
            future = pool.submit(task,i)#task任务要做20次,4个线程负责做这些事情
            print(future.result())#如果没有结果会一直等下去,导致了所有的任务都在串行
    #################
    Process-1在执行任务0
    0
    Process-2在执行任务1
    1
    Process-3在执行任务2
    4
    Process-4在执行任务3
    9
    Process-1在执行任务4
    16
    Process-2在执行任务5
    25
    Process-3在执行任务6
    36
    Process-4在执行任务7
    49
    Process-1在执行任务8
    64
    Process-2在执行任务9
    81
    Process-3在执行任务10
    100
    Process-4在执行任务11
    121
    Process-1在执行任务12
    144
    Process-2在执行任务13
    169
    Process-3在执行任务14
    196
    Process-4在执行任务15
    225
    Process-1在执行任务16
    256
    Process-2在执行任务17
    289
    Process-3在执行任务18
    324
    Process-4在执行任务19
    361
    
    
    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    from threading import currentThread
    from multiprocessing import current_process
    import time
    
    def task(i):
        print(f'{current_process().name}在执行任务{i}')
        time.sleep(1)
        return i**2
    if __name__ == '__main__':
        pool = ProcessPoolExecutor(4)#池子里有四个进程
        fu_list = []
        for i in range(20):
            future = pool.submit(task,i)#task任务要做20次,4个线程负责做这些事情
            # print(future.result())#如果没有结果会一直等下去,导致了所有的任务都在串行
            fu_list.append(future)
        pool.shutdown()#关闭了池的入口,会等待所有的任务执行完,结束阻塞.
        for fu in fu_list:
            print(fu.result())
    ###########################
    Process-1在执行任务0
    Process-2在执行任务1
    Process-3在执行任务2
    Process-4在执行任务3
    Process-1在执行任务4
    Process-2在执行任务5
    Process-3在执行任务6
    Process-4在执行任务7
    Process-1在执行任务8
    Process-2在执行任务9
    Process-3在执行任务10
    Process-4在执行任务11
    Process-1在执行任务12
    Process-2在执行任务13
    Process-3在执行任务14
    Process-4在执行任务15
    Process-1在执行任务16
    Process-2在执行任务17
    Process-3在执行任务18
    Process-4在执行任务19
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81
    100
    121
    144
    169
    196
    225
    256
    289
    324
    361
    
    
    

    4.2.1.2线程池

    4.2.1线程池

    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    from threading import currentThread
    from multiprocessing import current_process
    import time
    
    def task(i):
        print(f'{currentThread().name}在执行任务{i}')
        time.sleep(1)
        return i**2
    if __name__ == '__main__':
        pool = ThreadPoolExecutor(4)#池子里有四个线程
        fu_list = []
        for i in range(20):
            future = pool.submit(task,i)#task任务要做20次,4个线程负责做这些事情
            print(future.result())#如果没有结果会一直等下去,导致了所有的任务都在串行
    ##########################
    ThreadPoolExecutor-0_0在执行任务0
    0
    ThreadPoolExecutor-0_0在执行任务1
    1
    ThreadPoolExecutor-0_1在执行任务2
    4
    ThreadPoolExecutor-0_0在执行任务3
    9
    ThreadPoolExecutor-0_2在执行任务4
    16
    ThreadPoolExecutor-0_1在执行任务5
    25
    ThreadPoolExecutor-0_3在执行任务6
    36
    ThreadPoolExecutor-0_0在执行任务7
    49
    ThreadPoolExecutor-0_2在执行任务8
    64
    ThreadPoolExecutor-0_1在执行任务9
    81
    ThreadPoolExecutor-0_3在执行任务10
    100
    ThreadPoolExecutor-0_0在执行任务11
    121
    ThreadPoolExecutor-0_2在执行任务12
    144
    ThreadPoolExecutor-0_1在执行任务13
    169
    ThreadPoolExecutor-0_3在执行任务14
    196
    ThreadPoolExecutor-0_0在执行任务15
    225
    ThreadPoolExecutor-0_2在执行任务16
    256
    ThreadPoolExecutor-0_1在执行任务17
    289
    ThreadPoolExecutor-0_3在执行任务18
    324
    ThreadPoolExecutor-0_0在执行任务19
    361
    
    

    4.2.2异步

    异步:提交一个任务,不需要等待执行完毕,可以直接执行下一行代码。

    4.2.2.1异步进程池

    from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
    from threading import currentThread
    from multiprocessing import current_process
    import time
    
    
    def task(i):
        print(f'{current_process().name}在执行任务{i}')
        time.sleep(1)
        return i ** 2
    
    
    def parse(future):
        print(future.result())
    
    
    if __name__ == '__main__':
        pool = ProcessPoolExecutor(4)  # 池子里有四个进程
        for i in range(20):
            future = pool.submit(task, i)  # task任务要做20次,4个线程负责做这些事情
            # print(future.result())#如果没有结果会一直等下去,导致了所有的任务都在串行
            future.add_done_callback(parse)
            # 为当前任务绑定了一个函数,在当前任务执行结束的时候会触发这个函数,
            # 会把future对象作为参数传给函数
            # 这个称之为回调函数,处理完了回来就调用这个函数.
    #######################
    Process-1在执行任务0
    Process-2在执行任务1
    Process-3在执行任务2
    Process-4在执行任务3
    Process-1在执行任务4
    0
    Process-2在执行任务5
    1
    Process-3在执行任务6
    4
    Process-4在执行任务7
    9
    Process-1在执行任务8
    16
    Process-2在执行任务9
    25
    Process-3在执行任务10
    36
    Process-4在执行任务11
    49
    Process-1在执行任务12
    64
    Process-2在执行任务13
    81
    Process-3在执行任务14
    100
    Process-4在执行任务15
    121
    Process-1在执行任务16
    144
    Process-2在执行任务17
    169
    Process-3在执行任务18
    196
    Process-4在执行任务19
    225
    256
    289
    324
    361
    
    

    4.2.2.2异步线程池

    from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
    from threading import currentThread
    from multiprocessing import current_process
    import time
    
    
    def task(i):
        print(f'{currentThread().name}在执行任务{i}')
        time.sleep(1)
        return i ** 2
    
    
    def parse(future):
        print(future.result())
    
    
    if __name__ == '__main__':
        pool = ThreadPoolExecutor(4)  # 池子里有四个线程
        for i in range(20):
            future = pool.submit(task, i)  # task任务要做20次,4个线程负责做这些事情
            # print(future.result())#如果没有结果会一直等下去,导致了所有的任务都在串行
            future.add_done_callback(parse)
            # 为当前任务绑定了一个函数,在当前任务执行结束的时候会触发这个函数,
            # 会把future对象作为参数传给函数
            # 这个称之为回调函数,处理完了回来就调用这个函数.
    ##############################
    ThreadPoolExecutor-0_0在执行任务0
    ThreadPoolExecutor-0_1在执行任务1
    ThreadPoolExecutor-0_2在执行任务2
    ThreadPoolExecutor-0_3在执行任务3
    0
    ThreadPoolExecutor-0_0在执行任务4
    1
    ThreadPoolExecutor-0_1在执行任务5
    9
    ThreadPoolExecutor-0_3在执行任务6
    4
    ThreadPoolExecutor-0_2在执行任务7
    25
    ThreadPoolExecutor-0_1在执行任务8
    16
    ThreadPoolExecutor-0_0在执行任务9
    36
    ThreadPoolExecutor-0_3在执行任务10
    49
    ThreadPoolExecutor-0_2在执行任务11
    81
    ThreadPoolExecutor-0_0在执行任务12
    64
    ThreadPoolExecutor-0_1在执行任务13
    100
    ThreadPoolExecutor-0_3在执行任务14
    121
    ThreadPoolExecutor-0_2在执行任务15
    144
    ThreadPoolExecutor-0_0在执行任务16
    169
    ThreadPoolExecutor-0_1在执行任务17
    196
    ThreadPoolExecutor-0_3在执行任务18
    225
    ThreadPoolExecutor-0_2在执行任务19
    256
    289
    361
    324
    
    

    八、协程

    1. python的线程用的是操作系统原生的线程

    5.1什么是协程

    1. 协程:单线程下实现并发
    2. 并发:切换+保存状态
    3. 多线程:操作系统帮助实现,如果遇到io切换,执行时间过程也会切换,实现一个雨露均沾的效果
    • 什么样的协程是有有意义的?

      1. 遇到io切换的时候是有意义的
      2. 具体:协程的概念本质是程序猿抽象出来的,操作系统根本不知道协程的存在,也就是说来了一个线程我自己遇到io,我自己线程内部直接切到自己的另一个线程任务上,操作系统根本不知道这个过程。
    • 优点:

      1. 自己控制切换要比操作系统控制切换的快的多
    • 缺点:

      1. 对比多线程

        自己要检测所有的io,只要有一个阻塞整体都会跟着阻塞

      2. 对比多进程

        无法利用多核优势

    • 为什么要有协程?

      自己控制切换的速度比操作系统控制切换的快,降低了单个线程的io时间。

    import gevent
    import time
    
    
    def eat():
        print("eat 1")
        gevent.sleep(2)
        print('eat 2')
    
    
    def play():
        print("play 1")
        gevent.sleep(3)
        print("play 2")
    
    
    start = time.time()
    g1 = gevent.spawn(eat)
    g2 = gevent.spawn(play)
    g1.join()
    g2.join()
    end = time.time()
    print(end - start)
    ###################
    eat 1
    play 1
    eat 2
    play 2
    3.006275177001953
    
    
    from gevent import monkey;monkey.patch_all()####为协程打补丁
    # monkey.patch_all()
    import gevent,time
    def eat():
        print('eat 1')
        time.sleep(2)
        print('eat 2')
    def play():
        print('play 1 ')
        time.sleep(3)
        print('play 2')
    start = time.time()
    g1 = gevent.spawn(eat)
    g2 = gevent.spawn(play)
    g1.join()
    g2.join()
    end = time.time()
    print(end-start)
    #####################
    eat 1
    play 1 
    eat 2
    play 2
    3.004676580429077
    
    
    
  • 相关阅读:
    phpcms——列出父目录下的所有子目录问题
    chm格式文档不能阅读问题
    php学习 5 无限级分类
    phpcms——rss问题
    phpcms——评论页面修改
    phpcm后台评论管理修改评论列出条数
    php学习4 函数定义
    网站测试工具
    ASP.NET前台代码绑定后台变量方法总结收藏帖
    在asp.net网站下使用fckeditor 和fcfinder (包括修改fcfinder 来使上传文件按时间来命名和按用户分割文件)
  • 原文地址:https://www.cnblogs.com/SkyOceanchen/p/11643038.html
Copyright © 2020-2023  润新知