• 线程相关 GIL queue event 死锁与递归锁 信号量l


    #一、GIL全局解释器锁
    
    GIl是一个互斥锁:保证数据的安全(以牺牲效率来换取数据的安全)
    阻止同一个进程内多个线程同时执行(不能并行但能实现并发)
    并发:看起来像同时进行的)
    GIL全局解释器存在的原因是因为Cpython解释器的内存管理不是线程安全的
    
    垃圾回收机制:  (可以作为一种线程)
        1、引用计数
        2、标记清除
        3、分带回收
    
    同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行
    
    1、存在四个任务:计算密集型的任务 每个耗时10s
        单核情况下:多线程好一点,消耗的资源较少
        多核情况下:
            开四个进程:10s多一些
            一个进程下开启四个线程:40多秒
    2、存在四个任务:IO密集型的任务 每个任务IO 10s
        单核情况下:多线程好一些
        多核情况下:多线程好一些
    

      

    #计算密集与IO密集情况下线程与进程的耗时比较
    
    #计算密集型
    
    from multiprocessing import Process
    from threading import Thread
    import os,time
    
    def work():
        res = 0
        for i in range(12345678):
            res*=i
    
    if __name__ == '__main__':
        l = []
        print(os.cpu_count())  #查看cpu核数
        start_time = time.time()
        for i in range(4):   #4个进程或者4个线程去计算
            # p = Process(target=work) #run time is 3.224184513092041
            p = Thread(target=work)   #run time is 6.93839693069458
            l.append(p)
            p.start()
    
        for p in l:
            p.join()
        stop_time = time.time()
        print('run time is %s'%(stop_time-start_time))
    
    
    #IO密集型
    
    from multiprocessing import Process
    from threading import Thread
    import time
    
    def work():
        time.sleep(2)
    
    if __name__ == '__main__':
        l = []
        start_time = time.time()
        for i in range(4):
            # p = Process(target=work)  #2.416138172149658
            p = Thread(target=work) #2.0021145343780518
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop_time = time.time()
        print(stop_time-start_time)
    
    
    #结果验证正确
    

      

    #二、GIL与普通锁的对比
    
    #不加其他锁
    
    from threading import Thread
    import time
    
    n = 100
    def task():
        global n
        tmp = n
        time.sleep(0.1)
        n = tmp - 1
    
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(n)  #99
    #解释:当启动线程后,由于GIL的存在,一次只能运行一个线程,在其拿到n=100的值后,遇到IO,进入阻塞态,这时
    #系统强制结束该线程,换其他线程来执行,如此循环,导致所有线程拿到的都是n=100的值,最后一个线程结束后,返回的值是
    # n=100-1,则为99。 这个结果没有达到我们的初衷,因此需要依靠其他锁来保证数据的安全
    
    
    #加上其他锁
    
    from threading import Thread,Lock
    import time
    
    n=100
    mutex = Lock()
    def task():
        global n
        mutex.acquire()
        tmp = n
        time.sleep(0.1)
        n = tmp - 1
        mutex.release()
    
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t_list.append(t)
        t.start()
    for t in t_list:
        t.join()
    print(n) #0
    
    #解释:加上了锁以后,第一个线程先拿到了锁,拿到了n=100这个数据,然后遇到了IO阻塞,系统强制结束该进程,其他线程开始运行,但是
    #发现数据被加锁了,无法进一步运行,到时间了,系统又让其强制结束,如此往复,直到第一个进程再次拿到了执行权限,此时的阻塞已经过去,
    # 该进程进行相关运算,再释放锁,交给其他线程去竞争,如此循环往复,最后得到n = 0
    
    
    
    因此对于不同的数据,要想保证安全,需要加上不同的锁去处理
    GIL并不能保证数据的安全,他是对Cpython解释器加锁,针对的是线程
    保证的是在同一个进程中多个线程一个时间内只能运行一个线程
    

      

    #三、死锁与递归锁
    
    #死锁
    
    from threading import Thread,Lock
    import time
    
    mutex1 = Lock()
    mutex2 = Lock()
    
    class MyThead(Thread):
        def run(self):
            self.fun1()
            self.fun2()
    
        def fun1(self):
            mutex1.acquire()
            print('%s 抢到A锁'%self.name)
            mutex2.acquire()
            print('%s 抢到了B锁'%self.name)
            mutex2.release()
            print('%s释放了B锁'%self.name)
            mutex1.release()
            print('%s 释放了A锁'%self.name)
    
        def fun2(self):
            mutex2.acquire()
            print('%s抢到了B锁'%self.name)
            time.sleep(1)
            mutex1.acquire()
            print('%s 抢到了A锁'%self.name)
            mutex1.release()
            print('%s 释放了A锁'%self.name)
            mutex2.release()
            print('%s 释放了B锁'%self.name)
    
    for i in range(100):
        t = MyThead()
        t.start()
    
    #结果:
    
    Thread-1 抢到A锁
    Thread-1 抢到了B锁
    Thread-1释放了B锁
    Thread-1 释放了A锁
    Thread-1抢到了B锁
    Thread-2 抢到A锁
    然后程序就会卡着,此时就陷入了死锁 
    因为线程1拿到了B锁,线程2拿到了A锁,此时彼此拿着对方的命脉,不给对方活路,除非有外力,不然就会一直这样
    
    
    #递归锁
    
    from threading import RLock,Thread
    import time
    
    mutexA = mutexB =RLock()
    
    class MyThead(Thread):
        def run(self):
            self.fun1()
            self.fun2()
        def fun1(self):
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            mutexB.release()
            print('%s释放了B锁'%self.name)
            mutexA.release()
            print('%s释放了A锁'%self.name)
        def fun2(self):
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            time.sleep(1)
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)
            mutexA.release()
            print('%s释放了A锁'%self.name)
            mutexB.release()
            print('%s释放了B锁'%self.name)
    
    for i in range(10):
        t = MyThead()
        t.start()
    
    
    #总结
    
    自定义锁一次acquire必须对应一次release,不能连续acquire
    递归锁Rlock:这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
    直到一个线程所有的acquire都被release,其他的线程才能获得资源。
    mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,
    这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
    

      

    #四、信号量
    
    from threading import Thread,Semaphore
    import time,random
    
    sm = Semaphore(5)  #相当于厕所有5个茅坑
    
    def task(name):
        sm.acquire()
        print('%s正在蹲坑'%name)
        time.sleep(random.randint(1,5)) #模拟蹲坑耗时
        sm.release()
    
    for i in range(20):
        t = Thread(target=task,args=('巴豆%s号'%i,))
        t.start()
    #和普通的互斥锁区别在于,普通的是独立卫生间,所有人抢一个坑位
    #信号量 是公共卫生间,有多个茅坑,所有人抢多个坑位
    

      

    #五、线程 queue   使用import queue ,用法与进程的Queue一样
    #先进先出 queue.Queue
    #先进后出 queue.LifoQueue
    #优先级   queue.PriorityQueue
    
    
    import queue
    
    q = queue.Queue(3) #队列里最多放置的数据个数
    q.put(1)
    q.put(2)
    print(q.get()) #1
    print(q.get()) #2
    
    q = queue.LifoQueue(3)
    q.put(1)
    q.put(2)
    print(q.get()) #2
    print(q.get()) #1
    
    q = queue.PriorityQueue(3)
    q.put((10,'a'))
    q.put((-1,'a'))
    q.put((0,'a'))
    print(q.get()) #(-1, 'a')
    print(q.get()) #(0, 'a')
    print(q.get()) #(10, 'a')
    #对于优先级,元组里的第一个元素通常是数字,也可以是非数字之间去比较大小
    #比较的结果中,该元素越小,优先级越高
    

      

    #六、event事件
    
    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步
    的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程
    设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象
    , 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,
    它将唤醒所有等待这个Event对象的线程。
    如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
    
    event.isSet():返回event的状态值;
    
    event.wait():如果 event.isSet()==False将阻塞线程;
    
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    
    event.clear():恢复event的状态值为False。
    
    #多线程尝试连接MySQL
    from threading import Thread,Event
    import threading
    import time,random
    
    # event = Event()
    def conn_mysql():
        count = 1
        while not event.is_set():
            if count > 3:
                raise TimeoutError('链接超时')
            print('<%s>第%s尝试链接'%(threading.current_thread().getName(),count))
            event.wait(0.5)
            count += 1
        print('<%s>链接成功'%threading.current_thread().getName())
    
    def check_mysql():
        print('33[45m[%s]正在检查mysql33[0m'%threading.current_thread().getName())
        time.sleep(random.random())
        event.set()
    event = Event()
    conn1 = Thread(target=conn_mysql)
    conn2 = Thread(target=conn_mysql)
    check = Thread(target=check_mysql)
    
    conn1.start()
    conn2.start()
    check.start()
    

      

  • 相关阅读:
    工具推荐-根据IP地址精确定位经纬度(永久免费)
    VMware与Centos系统安装
    Python 之ConfigParser模块
    Python记录日志模块推荐-loguru!
    Excel 两列单元格合并超级链接的VBA 写法
    Shell脚本日志关键字监控+告警
    Python字符串及基本操作(入门必看)!!
    github release 下载文件慢、或者失败的解决方法
    Python字典及基本操作(超级详细)
    为什么使用 Containjs 模块化管理工具效率高?
  • 原文地址:https://www.cnblogs.com/changwenjun-666/p/10833217.html
Copyright © 2020-2023  润新知