zoukankan      html  css  js  c++  java
  • 2019.09.18学习整理 锁,多线程vs多进程

    2019.09.18学习整理

    线程锁

    同步锁(线程的互斥锁)

    多线程修改数据会造成混乱

    from threading import Thread,current_thread,Lock
    import time
    x = 0
    
    def task():
        global x
        for i in range(100000): # 最少10万级别才能看出来
            x = x+1   # 有可能右边的x刚拿到了0,
            # 发生线程不安全的原因:
            # t1 x+1 阶段 x = 0 保存了状态 cpu被切走  t2 x+1 x = 0 保存了状态 cpu被切走
            # 下次t1 继续运行代码 x = 0+1  下次t2 再运行代码的时候也是 x = 0+1
            #  也就说修改了两次 x 只加了一次1 。
            # time.sleep()
        # lock.release()
    if __name__ == '__main__':
        t_list = []
        for i in range(3):
            t = Thread(target=task)
            t_list.append(t)
            t.start()
        for i in t_list:
            i.join()
    
        print(x) # 99
    

    使用线程锁解决线程修改数据混乱问题

    from threading import Thread,current_thread,Lock
    import time
    x = 0
    lock = Lock()
    def task():
        global x
        lock.acquire()
        for i in range(100000): # 最少10万级别才能看出来  
            x = x+1   # 有可能右边的x刚拿到了0,
        lock.release()
    if __name__ == '__main__':
        t_list = []
        for i in range(3):
            t = Thread(target=task)
            t_list.append(t)
            t.start()
        for i in t_list:
            i.join()
    
        print(x) # 99
    

    死锁问题

    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,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    递归锁

    from threading import Thread,Lock,RLock
    # 递归锁 在同一个线程内可以被多次acquire
    # 如何释放 内部相当于维护了一个计数器 也就是说同一个线程 acquire了几次就要release几次
    # mutex1 = Lock()
    # mutex2 = Lock()
    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

    同进程的一样

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

    from threading import Thread,Semaphore
    import threading
    import time
    
    
    def task():
        sm.acquire()
        print(f"{threading.current_thread().name} get sm")
        time.sleep(3)
        sm.release()
    
    if __name__ == '__main__':
        sm = Semaphore(5) # 同一时间只有5个进程可以执行。
        for i in range(20):
            t = Thread(target=task)
            t.start()
    

    GIL

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

    为什么要有GIL?

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

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

    分析:

    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

    方案一:开启四个进程

    方案二:一个进程下,开启四个线程

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

    每个都要计算10s
    多线程
    在同一时刻只有一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40.ns
    多进程
    可以并行的执行多个线程,10s+开启进程的时间

    io密集型 推荐多线程

    4个任务每个任务90%大部分时间都在io.
    每个任务io10s 0.5s
    多线程
    可以实现并发,每个线程io的时间不咋占用cpu, 10s + 4个任务的计算时间
    多进程
    可以实现并行,10s+1个任务执行的时间+开进程的时间

    多进程vs多线程

    from threading import Thread
    from multiprocessing import Process
    import time
    
    # 计算密集型
    # def work1():
    #     res=0
    #     for i in range(100000000): #1+8个0
    #         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密集型
    # 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
    
    
  • 相关阅读:
    Visual Studio 插件的开发
    EntityFramework 4.x 使用中遇到的问题 (2)
    F#学习笔记核心类型(二)
    F#学习笔记函数式编程(一)
    EntityFramework 4.x 使用中遇到的问题 (1)
    vue3项目一些小坑
    记一种拖拉拽编排流程的思路
    隐藏Jenkinsfile敏感信息
    Jenkins条件判断
    Jenkins 流水线语法自动部署
  • 原文地址:https://www.cnblogs.com/zhangmingyong/p/11542783.html
Copyright © 2011-2022 走看看