• 多线程(2)


    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 保存状态,就被切了
            # t2 的 x拿到0 进行+1   1
            # t1 又获得运行了 x = 0  +1  1
            # 思考:一共加了几次1?加了两次  真实运算出来的数字本来应该+2,实际只+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)
    
    
    # 使用线程锁解决线程修改数据混乱问题
    from threading import Thread,Lock
    
    x = 0
    mutex = Lock()
    def task():
        global x
        mutex.acquire()
        for i in range(200000):
            x = x + 1
            # t1 的 x刚拿到0 保存状态,就被切了
            # t2 的 x拿到0 进行+1   1
            # t1 又获得运行了 x = 0  +1  1
            # 思考:一共加了几次1?加了两次  真实运算出来的数字本来应该+2,实际只+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)
    

    2. 死锁问题

    from threading import Thread,Lock
    import time
    
    mutex1 = Lock()
    mutex2 = Lock()
    class Myt(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 = Myt()
        t.start()
    
    '''
    两个线程
    线程1拿到了(锁头2)想要往下执行需要(锁头1)
    线程2拿到了(锁头1)想要往下执行需要(锁头2)
    互相都拿到了彼此想要往下执行的必需条件,互相都不放手里的锁头
    
    解决方法:
    递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
    直到一个线程所有的acquire都被release,其他的线程才能获得资源
    '''
    
    # 递归锁 在同一个线程内可以被多次acquire
    # 如何释放 内部相当于维护了一个计数器 也就是说同一个线程,acquire了几次就要release几次
    from threading import Thread,RLock
    import time
    
    # #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,
    # 这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
    mutex1 = RLock()
    mutex2 = mutex1
    
    
    class Myt(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 = Myt()
        t.start()
    

    3. 信号量

    from threading import Thread,currentThread,Semaphore
    import time
    
    def task():
        sm.acquire()
        print(f'{currentThread().name} 在执行')
        time.sleep(3)
        sm.release()
    
    sm = Semaphore(5)   # 同一时间只有5个进程可以执行
                        # 同时只有5个线程可以获得semaphore,即可以限制最大连接数为5
    for i in range(15):
        t = Thread(target=task)
        t.start()
    
    '''
    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器-1;
    调用release() 时内置计数器+1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
    
    与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程。
    '''
    

    4. GIL

    '''
    在Cpython解释器中有一把GIL锁(全局解释器锁),GIL锁本质是一把互斥锁
    导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势
    同一个进程下多个线程只能实现并发不能实现并行
    
    为什么要有GIL?
    因为cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁
    
    导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势
    
    ### 保护不同的数据的安全,就应该加不同的锁。
    
    分析:
    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
    方案一:开启四个进程
    方案二:一个进程下,开启四个线程
    
    计算密集型  推荐使用多进程
    每个都要计算10s
    多线程:
    在同一时刻只有一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40.n s
    多进程:
    可以并行地执行多个线程,10s+开启进程的时间
    
    io密集型 推进多线程
    4个任务每个任务90%时间都在io
    每个任务io 10s 执行0.5s
    多线程:
    可以实现并发,每个线程io的时间不咋占用cpu,10sj+ 4个任务的计算时间
    多进程:
    可以实现并行,10s+1个任务执行的时间+开进程的时间
    
    如果一个线程抢掉了GIL,如果遇到io或者执行时间过长(cpu被剥夺),会强行释放掉GIL锁,以便其他的线程抢占GIL
    '''
    
    from threading import Thread
    from multiprocessing import Process
    import time
    
    # 计算密集型
    def work1():
        res = 0
        for i in range(100000000):
            res *= i
    
    if __name__ == '__main__':
        t_list = []
    
        start = time.time()
        for i in range(4):
            # t = Thread(target=work1)
            t = Process(target=work1)
            t_list.append(t)
            t.start()
        for t in t_list:
            t.join()
        end = time.time()
        # print('多线程:',end-start)   # 多线程: 28.847110986709595
        print('多进程:',end-start)   # 多进程: 19.194590091705322
    
    
    
    # io密集型
    def work1():
        x = 1+1
        time.sleep(5)
    
    if __name__ == '__main__':
        t_list = []
        start = time.time()
        for i in range(4):
            # t = Thread(target=work1)
            t = Process(target=work1)
            t_list.append(t)
            t.start()
        for i in t_list:
            t.join()
        end = time.time()
        # print('多线程:',end-start)   # 多线程: 5.003501653671265
        print('多进程:',end-start)   # 多进程: 6.331640005111694
    
  • 相关阅读:
    GZOI 2017配对统计 树状数组
    关于线段树的一些问题
    BZOJ 压力 tarjan 点双联通分量+树上差分+圆方树
    洛谷4552 差分
    洛谷5026 Lycanthropy 差分套差分
    【锁】MySQL和Oracle行锁比较
    oracle体系结构
    【加密】RSA验签及加密
    【Shiro】SpringBoot集成Shiro
    【Eureka】实现原理
  • 原文地址:https://www.cnblogs.com/yushan1/p/11543238.html
Copyright © 2020-2023  润新知