• 并发编程(三) GIL锁和其他知识


    1.全局解释器锁GIL

    什么是GIL

      GIL是全局解释器锁他规定了每个线程在被CPU执行前都要获得一个GIL,并且同一时刻只有一个线程被执行

    为什么要有GIL

      因为CPython解释器的内存管理不是线程安全的,所以在CPython中增加的一个GIL锁

    线程释放GIL锁的情况

      在I/O操作等会引起阻塞状态的操作时,会暂时释放GIL,其他线程可以获取GIL继续执行

    python的多线程和多进程的使用场景

      1.当CPU处于多任务计算密集型的情况下

        单核:两者差不多,但是多线程更加省资源

        多核:由于多线程不能利用多核的优势,所以使用多进程更有优势

      2.当CPU处于多任务I/O密集型的情况下

        单核:和上述一样,多线程更省资源

        多核:由于I/O密集型的任务耗费的时间大部分在I/O操作上,并且cpu在阻塞状态时可以暂时释放GIL,所以还是多线程更省资源(开启进程是耗费资源,当需要进行的任务过多,多进程耗费的时间会大大超过多线程)

     1 # 计算密集型
     2 from multiprocessing import Process
     3 import os,time
     4 def work():
     5     res=0
     6     for i in range(100000000):
     7         res*=i
     8 
     9 
    10 if __name__ == '__main__':
    11     l=[]
    12     print(os.cpu_count())  # 本机为8核
    13     start=time.time()
    14     for i in range(6):
    15         p=Process(target=work)  # 在当前机器上,多进程耗时 9.134473323822021s
    16         # p=Thread(target=work)  # 在当前机器上,多线程耗时 51.49368095397949s
    17         l.append(p)
    18         p.start()
    19     for p in l:
    20         p.join()
    21     stop=time.time()
    22     print('run time is %s' %(stop-start))
    多核计算机计算密集型下多进程和多线程的区别
     1 from multiprocessing import Process
     2 import os,time
     3 def work():
     4     time.sleep(2)
     5 
     6 if __name__ == '__main__':
     7     l=[]
     8     print(os.cpu_count()) #本机为8核
     9     start=time.time()
    10     for i in range(400):
    11         p=Process(target=work)  # 在当前机器上,多进程耗时12.091996908187866s多,大部分时间耗费在创建进程上
    12         # p=Thread(target=work)  # 在当前机器上,多线程耗时2.050579309463501s
    13         l.append(p)
    14         p.start()
    15     for p in l:
    16         p.join()
    17     stop=time.time()
    18     print('run time is %s' %(stop-start))
    多核计算机I/O密集型下多进程和多线程的区别

    由上述计算结果表明,一般情况下,只有当多核计算机在执行计算密集型操作时使用多进程,但是这不是一成不变的,要视具体情况而定,并且实际开发中我们都是多进程+多线程结合使用

     多线程I/O操作时不会耗费时间

     1 from threading import Thread
     2 import time
     3 
     4 n = 100
     5 
     6 def task():
     7     global n
     8     temp = n
     9     time.sleep(1)
    10     '''
    11     模拟I/O操作,当I/O进行时i,线程会暂时释放GIL,其他线程可以获得,他们都进行到这一步,n = 100
    12     I/O操作结束,开始执行每个线程的n-1得到的都是99,所以结果是99
    13     
    14     如果没有I/O操作,那因为GIL锁的原因,每个线程都需要执行完才会释放GIL,
    15     故第二个线程获得的n是第一个线程-1后的结果,所以结果是0
    16     '''
    17     n = temp - 1
    18 
    19 l = []
    20 for i in range(100):
    21     t = Thread(target=task)
    22     t.start()
    23     l.append(t)
    24 for t in l:
    25     t.join()
    26 print(n)
    GIL有无I/O操作时的区别

    time模拟I/O操作,当I/O进行时i,线程会暂时释放GIL,其他线程可以获得,他们都进行到这一步,n = 100I/O操作结束,开始执行每个线程的n-1得到的都是99,所以结果是99

    如果没有I/O操作,那因为GIL锁的原因,每个线程都需要执行完才会释放GIL,故第二个线程获得的n是第一个线程-1后的结果,所以结果是0

     

    2.死锁

    什么是死锁

      指的是两个或多个进程或线程在执行过程中,因争夺资源而造成了一种互相等待的问题,如果没有外力的作用,它们都无法执行下去,这时就是处于死锁状态

     1 from threading import Thread,Lock
     2 
     3 mutex = Lock()
     4 
     5 def task():
     6     mutex.acquire()
     7     print('hello world')
     8     mutex.acquire()  
     9     '''
    10     想要获得这个锁,需要等到上面的人释放锁,但是他想要释放锁必须先获得锁,这就形成了死锁
    11     '''
    12     print('hi')
    13     mutex.release()
    14     mutex.release()
    15 
    16 t = Thread(target=task)
    17 t.start()
    死锁的小例子

    线程1开始执行,time.sleep(1)模拟I/O操作,当第一个线程执行到这时,暂停了1秒,释放了GIL锁

    线程2开始执行,当线程2抢到A锁,需要抢B锁,但是B锁在线程1中,这时线程1开始执行,需要故两个线程互相等待,形成了死锁

    3.递归锁

    什么是递归锁

      RLock递归锁,可以被第一个抢到锁的人连续的acquire()和release()每次acquire,身上的计数加1,每次release身上的计数减1,只要身上的引用计数不为0,其他任何人都不能抢

     1 from threading import Thread,RLock
     2 
     3 mutex = RLock()
     4 
     5 def task():
     6     mutex.acquire()
     7     print('hello world')
     8     mutex.acquire()
     9     '''
    10     使用递归锁,可以避免形成死锁
    11     '''
    12     print('hi')
    13     mutex.release()
    14     mutex.release()
    15 
    16 t = Thread(target=task)
    17 t.start()

     

     1 from threading import Thread,Lock,RLock
     2 import time
     3 
     4 # mutexA = Lock()
     5 # mutexB = Lock()
     6 
     7 mutexA = RLock()
     8 '''
     9 RLock递归锁,可以被第一个抢到锁的人连续的acquire()和release()
    10 每次acquire,身上的计数加1,每次release身上的计数减1
    11 只要身上的引用计数不为0,其他任何人都不能抢
    12 '''
    13 
    14 class MyThread(Thread):
    15     def run(self):
    16         self.task()
    17         self.task1()
    18 
    19     def task(self):
    20         mutexA.acquire()
    21         print('%s抢到了A锁'%self.name)
    22         mutexA.acquire()
    23         print('%s抢到了B锁'%self.name)
    24         mutexA.release()
    25         print('%s释放了A锁' % self.name)
    26         mutexA.release()
    27         print('%s释放了B锁' % self.name)
    28 
    29     def task1(self):
    30         mutexA.acquire()
    31         print('%s抢到了B锁' % self.name)
    32         time.sleep(1)
    33         mutexA.acquire()
    34         print('%s抢到了A锁' % self.name)
    35         mutexA.release()
    36         print('%s释放了A锁' % self.name)
    37         mutexA.release()
    38         print('%s释放了B锁' % self.name)
    39 
    40 for i in range(4):
    41     t = MyThread()
    42     t.start()
    递归锁的小例子

    递归锁可以一定程度上解决死锁

    4.信号量

    一个互斥锁能控制只有抢到锁的线程在执行锁内的操作,

    而信号量可以控制指定数量的线程执行锁内的操作

     1 from threading import Thread,Semaphore
     2 import time
     3 
     4 s = Semaphore(3)  # 允许最大3个线程同时执行
     5 
     6 def task(i):
     7     s.acquire()
     8     print('%s正在执行'%i)
     9     time.sleep(1)
    10     s.release()
    11 
    12 for i in range(10):
    13     t = Thread(target=task,args=(i,))
    14     t.start()

    5.event事件

    event通过标志位来判断运行的状态

    Event用法:
    event=threading.Event() #设置一个事件实例
    event.set() #设置标志位
    event.clear() #清空标志位
    event.wait()  #等待设置标志位
    event.is_set  #判断标志位是否被设置
     1 from threading import Thread,Event
     2 import time
     3 import random
     4 
     5 e = Event()  # 生成一个event对象
     6 
     7 def jump():
     8     print('飞机起飞,准备跳伞')
     9     time.sleep(1.1)
    10     print('倒计时5秒,机舱打开')
    11     for i in range(6):
    12         print('
    %s'%(5-i),end= '')
    13         time.sleep(1)
    14     e.set()  # 发送信号
    15     print('')
    16     print('机舱打开,开始跳伞')
    17 
    18 
    19 def people(i):
    20     time.sleep(random.random())
    21     print('伞兵%s号,准备完毕'%i)
    22     e.wait()  # 等待信号
    23     time.sleep(random.random())
    24     print('伞兵%s号,开始跳伞'%i)
    25 
    26 t = Thread(target=jump)
    27 t.start()
    28 
    29 for i in range(1,10):
    30     t1 = Thread(target=people,args=(i,))
    31     t1.start()

    6.队列补充

    队列是管道+锁,使用队列就不需要自己手动操作锁的问题
    因为锁操作的不好极容易产生死锁现象

    队列:
        import queue  # 导入模块
    
        queue.Queue()  # 生成队列对象,先进先出
        queue.LifoQueue()  # 生成堆栈对象,后进先出
        queue.PriorityQueue()  # 优先级队列,数字越小,优先级越高
     1 from queue import Queue,LifoQueue,PriorityQueue
     2 
     3 q = Queue()  # 生成队列对象,先进先出
     4 
     5 q.put(1)
     6 q.put(2)
     7 q.put(3)
     8 print(q.get())
     9 print(q.get())
    10 print(q.get())
    11 
    12 q = LifoQueue()  # 堆栈,后进先出
    13 q.put(1)
    14 q.put(2)
    15 q.put(3)
    16 print(q.get())
    17 print(q.get())
    18 print(q.get())
    19 
    20 q = PriorityQueue()  # 优先级队列,根据传值的大小,小的先出队
    21 # 传入元组,第一个值限制了取值时的优先级
    22 q.put((1,1))
    23 q.put((1,5))
    24 q.put((1,3))
    25 print(q.get())
    26 print(q.get())
    27 print(q.get())
    队列补充

     32

  • 相关阅读:
    Dictionary<string, object>不区分大小写
    修改Windows远程桌面端口
    LookupError: unknown encoding: cp65001的问题
    Git命令基本操作备忘
    MariaDB10 修改默认密码
    android去除标题栏
    解决Eclipse中Android SDK Manager图标不见了的问题
    HTML邮件注意事项
    不同内核浏览器的差异以及浏览器渲染简介(转)
    DIV+CSS两种盒子模型
  • 原文地址:https://www.cnblogs.com/sxchen/p/11354501.html
Copyright © 2020-2023  润新知