zoukankan      html  css  js  c++  java
  • 叁拾壹

    一、线程锁

    ​ 运行以下代码:

    from threading import Thread,Lock
    
    x = 0
    mutex = Lock()
    def task():
        global x
        for i in range(200000):
            x = 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)
        
    440215
    

    ​ 正常运行代码应该得到600000,而实际结果为440215。这是因为代码过长,cpu进行了代码的切换,切入的时间可能恰好是运算的过程中,使得一次完整的计算没有完成,便直接运行其他线程。当再次切回这个线程时,程序会接着上一次的结果运行,造成切出去时运算的数据丢失。

    ​ 如果计算量少,则可能不会出现上述问题,计算量越大,出现的结果偏差可能越大。

    ​ 因此为了解决数据丢失的问题,需要使用互斥锁。使用.acquire()方法获取对象,使用.release()释放对象,会完整运行完其中的线程后再进行其他线程。与进程一样,将多线程并行改为串行,保证了数据的安全。

    from threading import Thread,Lock
    
    x = 0
    mutex = Lock()
    def task():
        global x
        mutex.acquire()
        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)
    

    二、死锁问题

    ​ 假设两个线程分别拿到了两个锁,而且并未释放,它们都需要拿到对方的锁进程才可以进行下去。这样双方都拿不到彼此需要的锁,导致程序不能进行下去,这就是锁死问题。如下程序,task2中,time.sleep使线程切换,导致另一个线程也得到一把锁,两个线程相互锁死。

    from threading import Thread,Lock
    mutex1 = Lock()
    mutex2 = Lock()
    import time
    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(0.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-2 抢到了 锁1 
    

    三、递归锁

    ​ 可以使用递归锁来解决死锁的问题。在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    ​ 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

    from threading import Thread,RLock
    
    mutex1 = RLock()
    mutex2 = mutex1
    
    import time
    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()
    

    四、信号量

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

    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-8 在执行
    Thread-6 在执行
    Thread-9 在执行
    Thread-10 在执行
    Thread-11 在执行
    Thread-12 在执行
    Thread-13 在执行
    Thread-14 在执行
    Thread-15 在执行
    

    五、全局解释器锁GIL

    ​ C python自带垃圾回收机制,在线程运行时如果垃圾回收机制也同时运行,会导致相互“抢夺”数据,因此GIL锁可以使同一进程同一时间只能运行一个线程,这样就能保证线程的数据安全,但是导致了无法使用多核优势,减低运算效率。

    ​ 如果一个线程抢掉了GIL,如果遇到io或者执行时间过长(cpu被剥夺),会强行释放掉GIL锁,以便其他的线程抢占GIL。

    六、多进程与多线程的适用场景

    ​ 假设有四个任务要进行处理,那么有两种方案可以选择,开启四个进程和一个进程下开启四个线程。

    ​ 计算密集型推荐使用多进程;

    ​ 假设每个人物要进行计算10秒,如果采用多线程,因为同一时刻只能执行一个线程,所以串行4个线程,每个都需要10s,一共40s。

    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) # 多线程 15.413789510726929
        print('多进程',end-start) # 多进程 4.711405515670776
    

    ​ io密集型推荐使用多线程;

    ​ 假设4个任务90%时间处于io状态,采用多线程,每个线程io时间都不占用cpu,可以执行其他进程,因此所需时间为10s加运算四个就成时间。而采用多进程,可以实行并行,但是花费时间为10s加一个任务执行时间加开启进程的时间。

    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 t in t_list:
            t.join()
        end = time.time()
        print('多线程',end-start) #  多线程 5.002625942230225
        # print('多进程',end-start) # 多进程 5.660863399505615
    
  • 相关阅读:
    devexpress13学习系列(三)PDFViewer(3)
    devexpress13学习系列(一)PDFViewer(2)
    devexpress13学习系列(一)PDFViewer(1)
    Android图像处理之Bitmap类(zz)
    laravel 制作购物车流程
    数据类型转换
    http返回值
    前段框架
    开启GD拓展
    PHP初级程序员出路
  • 原文地址:https://www.cnblogs.com/tangceng/p/11545366.html
Copyright © 2011-2022 走看看