• 多线程并发, GIL, 死锁问题及解决


    多线程实现TCP服务端并发

    服务端---封装接口思想

    import threading
    import socket
    
    
    def server_interface():
        server = socket.socket()
        server.bind(('127.0.0.1', 8888))
        server.listen()
    
        def multi_handle():
            new_server_link, address = server.accept()
            print(address)
    
            while True:
                try:
    
                    res = new_server_link.recv(1024).decode('utf-8')
                    print(res)
    
                    new_server_link.send(res.upper().encode('utf-8'))
    
                    if res == 'q':
                        return
    
                except Exception as e:
                    print(e)
                    return
    
        for i in range(10):
            t = threading.Thread(target=multi_handle)
            t.start()
    
    
    if __name__ == '__main__':
        server_interface()
    
    

    GIL全局解释器锁

    什么是GIL?---global interpreter lock

    • 在Cpython解释器中有一把GIL全局解释器锁,本质上是一把互斥锁
    • 可以使同一进程下,同一时刻只能运行一个线程
      • 优点:执行I/O密集型任务效率和多进程区别不大,反而更节省资源
      • 缺点:执行计算密集型任务无法利用多核优势
    • 同一进程下多个线程只能实现并发不能实现并行
    • 如果一个线程抢占了GIL,当遇到I/O或者执行时间过长时,会强行释放掉GIL锁,使其他线程可以抢占GIL执行任务,从GIL切换出来的线程会有GIL计数,不会被垃圾回收线程回收

    为什么要有GIL?

    • 因为Cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁
    • 如果没有GIL,则python解释器的垃圾回收线程和任务线程可以并行,在任务线程I/O时,某些变量值引用计数为0,很可能会被回收, 导致数据不安全

    死锁问题及解决

    死锁问题

    • 两个线程
    • 线程1拿到了锁头2,想要往下执行释放锁头2需要锁头1
    • 线程2拿到了锁头1,想要往下执行释放锁头1需要锁头2
    • 它们互相都拿到彼此想要往下执行释放锁的必需条件,所以造成了死锁问题

    解决:递归锁

    • 递归锁在同一个线程内可以被多次acquire
    • 如何释放: 内部相当于维护了一个计数器,也就是说同一个线程acquire了几次就要release几次
    import threading
    import time
    
    
    class MyThread(threading.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')
            mutex1.release()
            print(f'{self.name}释放了 锁1')
            mutex2.release()
            print(f'{self.name}释放了 锁2')
    
        def task2(self):
            mutex2.acquire()
            print(f'{self.name}拿到了 锁2')
    
            time.sleep(0.01)  # 阻塞线程使下一个线程拿到锁头1
    
            mutex1.acquire()
            print(f'{self.name}拿到了 锁1')
            mutex2.release()
            print(f'{self.name}释放了 锁2')
            mutex1.release()
            print(f'{self.name}释放了 锁1')
    
    
    mutex1 = threading.Lock()
    mutex2 = threading.Lock()
    
    # 解决死锁问题:递归锁
    mutex1 = mutex2 = threading.RLock()  # 两个变量引用同一个递归锁对象的地址,该对象引用计数为2
    
    for i in range(3):
        t = MyThread()
        t.start()
    
    '''
    # 死锁:
    Thread-1拿到了 锁1
    Thread-1拿到了 锁2
    Thread-1释放了 锁1
    Thread-1释放了 锁2
    Thread-1拿到了 锁2
    Thread-2拿到了 锁1
    '''
    
    

    信号量

    import threading
    import time
    
    
    def task():
        sm.acquire()  # 加信号量,信号量本质上是锁
    
        print(f'线程{threading.current_thread().name}开始...')
        print('*' * 50)
    
        time.sleep(3)
    
        print(f'线程{threading.current_thread().name}结束...')
        print('*' * 50)
    
        sm.release()  # 释放信号量
    
    
    if __name__ == '__main__':
        sm = threading.Semaphore(5)  # 实例化得到信号量对象,参数控制能够同时并发的线程数量
    
        for i in range(25):
            t = threading.Thread(target=task)
            t.start()
    
    
  • 相关阅读:
    Python-快速入门
    Python-面向对象编程
    python-模块
    .net mvc onexception capture; redirectresult;
    a c lang in linux
    上海哪里有学陈氏太极拳?
    【Origin】 叹文
    【Origin】 碑铭
    【Origin】 偶题 之 抒意
    【Origin】答友朋关切书
  • 原文地址:https://www.cnblogs.com/-406454833/p/11753273.html
Copyright © 2020-2023  润新知