zoukankan      html  css  js  c++  java
  • 多线程

    一、GIL全局解释器锁

    1.什么是GIL

    官方给出的解释是:在CPython中,这个全局解释器锁,也成为GIL,是一个互斥锁,防止多个线程在同一时间执行python字节码,这个锁是非常重要的,因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,所以即使它影响了程序效率也无法将其直接去除

    ★★★总结:在CPython中,GIL会把线程的并行变成串行,导致效率降低。

    需要知道的是,解释器并不只有CPython,还有PyPy,JPython等等。GIL也仅仅存在于CPython中,这并不是Python这门语言的问题,而是CPython解释器的问题!

    2.GIL的加锁与解锁时机

    加锁的时机:在调用解释器时立即加锁

    解锁时机:

    ♦ 当前线程遇到了IO时释放

    ♦ 当前线程执行时间超过设定值的释放

    3.GIL性能讨论

    GIL的优点:

        保证了CPython中的内存管理是线程安全的

    GIL的缺点:

      互斥锁的特性使得多线程无法并行

    直接上代码测试:

    1.计算密集型

     1 from multiprocessing import Process
     2 from threading import Thread
     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())  # 本机为6核
    13     start=time.time()
    14     for i in range(6):
    15         p=Process(target=work) #耗时  4.732933044433594
    16         p=Thread(target=work) #耗时 22.83087730407715
    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))

      2.IO密集型

     1 from multiprocessing import Process
     2 from threading import Thread
     3 import threading
     4 import os,time
     5 def work():
     6     time.sleep(2)
     7 
     8 
     9 if __name__ == '__main__':
    10     l=[]
    11     print(os.cpu_count()) #本机为6核
    12     start=time.time()
    13     for i in range(4000):
    14         p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
    15         # p=Thread(target=work) #耗时2.051966667175293s多
    16         l.append(p)
    17         p.start()
    18     for p in l:
    19         p.join()
    20     stop=time.time()
    21     print('run time is %s' %(stop-start))

      ★★总结:由上得到,当执行计算密集型的代码时,多进程的运行速度要比多线程快。而当处理IO密集型的代码时,多线程的运行速度要快于多进程的。

    二、GIL与普通的互斥锁

     1 from threading import Thread,Lock
     2 import time
     3 
     4 a = 0
     5 def task():
     6     global a
     7     temp = a
     8     time.sleep(0.01) 
     9     a = temp + 1
    10 
    11 t1 = Thread(target=task)
    12 t2 = Thread(target=task)
    13 t1.start()
    14 t2.start()
    15 t1.join()
    16 t2.join()
    17 print(a)
    加锁前

    过程分析:

    1.线程1获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL

    2.线程2获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL

    3.线程1睡醒后获得CPU执行权,并获取GIL执行代码,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL

    4.线程2睡醒后获得CPU执行权,并获取GIL执行代码,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL,最后a的值也就是1

    之所以出现问题就是因为两个线程在并发的执行用一段代码,解决方案就是加锁。

     1 from threading import Thread,Lock
     2 import time
     3 
     4 lock = Lock()
     5 a = 0
     6 def task():
     7     global a
     8     lock.acquire()
     9     temp = a
    10     time.sleep(0.01)
    11     a = temp + 1
    12     lock.release()
    13 
    14 t1 = Thread(target=task)
    15 t2 = Thread(target=task)
    16 t1.start()
    17 t2.start()
    18 t1.join()
    19 t2.join()
    20 print(a)
    加锁后

    过程分析:

    1.线程1获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock

    2.线程2获得CPU执行权,并获取GIL锁,尝试或lock失败,无法执行,释放CPU并释放GIL

    3.线程1睡醒后获得CPU执行,并获取GIL继续执行代码,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1

    4.线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放lock

    5.线程2睡醒后获得CPU执行权,获取GIL继续执行代码,将temp的值1+1赋值给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2

    三、死锁

     1 from threading import Thread,Lock,current_thread
     2 import time
     3 
     4 mutexA = Lock()
     5 mutexB = Lock()
     6 
     7 class MyThread(Thread):
     8     def run(self): # 创建线程自动触发run方法,run方法内调用了func1 func2相当于也是自动触发
     9         self.func1()
    10         self.func2()
    11 
    12     def func1(self):
    13         mutexA.acquire()
    14         print('%s抢到了A锁'%self.name) #self.name 等价于 current_thread
    15         mutexB.acquire()
    16         print('%s抢到了B锁'%self.name)
    17         mutexB.release()
    18         print('%s释放了B锁'%self.name)
    19         mutexA.release()
    20         print('%s释放了A锁'%self.name)
    21 
    22     def func2(self):
    23         mutexB.acquire()
    24         print('%s抢到了B锁'%self.name)
    25         time.sleep(1)
    26         mutexA.acquire()
    27         print('%s抢到了A锁'%self.name)
    28         mutexA.release()
    29         print('%s释放了A锁'%self.name)
    30         mutexB.release()
    31         print('%s释放了B锁'%self.name)
    32 
    33 for i in range(10):
    34     t = MyThread()
    35     t.start()
    死锁现象

      死锁原因:线程1执行func1,首先抢到了A锁,随后抢到了B锁,然后释放了B锁,最后释放了A锁。接着执行线程2,抢到了B锁,这时遇到了IO操作,中途睡了1秒,在这时间内线程2开始执行func1,首先抢到了A锁,但是紧接着在抢B锁时,失败了。因为此时A锁还被线程1抢着,1秒后线程1睡醒了,要接着抢A锁了,但此时A锁被线程2抢占了,线程2只有抢到B锁,才能继续往下走,而线程1必须抢到A锁才能继续执行下一步。那么此时就陷入了死锁状态。

    四、递归锁

     1 from threading import Thread,RLock,current_thread
     2 import time
     3 
     4 mutexA = mutexB = RLock() # A B现在是同一把锁
     5 
     6 
     7 class MyThread(Thread):
     8     def run(self): # 创建线程自动触发run方法,run方法内调用了func1 func2相当于也是自动触发
     9         self.func1()
    10         self.func2()
    11 
    12     def func1(self):
    13         mutexA.acquire()
    14         print('%s抢到了A锁'%self.name) #self.name 等价于 current_thread
    15         mutexB.acquire()
    16         print('%s抢到了B锁'%self.name)
    17         mutexB.release()
    18         print('%s释放了B锁'%self.name)
    19         mutexA.release()
    20         print('%s释放了A锁'%self.name)
    21 
    22     def func2(self):
    23         mutexB.acquire()
    24         print('%s抢到了B锁'%self.name)
    25         time.sleep(1)
    26         mutexA.acquire()
    27         print('%s抢到了A锁'%self.name)
    28         mutexA.release()
    29         print('%s释放了A锁'%self.name)
    30         mutexB.release()
    31         print('%s释放了B锁'%self.name)
    32 
    33 for i in range(10):
    34     t = MyThread()
    35     t.start()
    递归锁

       ▶ Rlock可以被第一个抢到锁的人连续的acquire和release

    ▶ 每acquire一次锁身上的计数加1

    ▶ 每release一次锁身上的计数减1

    ▶ 只要锁的计数不为0 其他人都不能抢

    五、信号量

     1 from threading import Semaphore,Thread
     2 import time
     3 import random
     4 
     5 sm = Semaphore(5) # 就好比制造了一个五个坑位的厕所
     6 
     7 def task(name):
     8     sm.acquire()
     9     print('%s占了一个位子'%name)
    10     time.sleep(1)
    11     sm.release()
    12 
    13 for i in range(40):
    14     t = Thread(target=task,args=(i,))
    15     t.start()
    信号量

      与递归锁的不同点:递归锁只允许一个线程抢占,而信号量可以指定抢占的数量

    六、event事件

     1 from threading import Thread,Event
     2 import time
     3 
     4 #先生成一个对象
     5 e = Event()
     6 n = 3
     7 def light():
     8     global n
     9     print('红灯正亮着')
    10     for i in range(3):
    11         time.sleep(1)
    12         print('
    %s'%n,end='')
    13         n -= 1
    14     time.sleep(1)
    15     e.set() #发信号
    16     print('
    Go!
    ')
    17 
    18 def car(name):
    19     print('%s正在烧胎!'%name)
    20     e.wait() #等待信号
    21     print('%s弹射起步!'%name)
    22 
    23 t = Thread(target=light)
    24 t.start()
    25 
    26 for i in range(1,11):
    27     t = Thread(target=car,args=('秋名山车神%s'%i,))
    28     t.start()
    event飙车

    七、线程queue

    首先带着一个疑问,同一个线程中多个线程本身就可以共享数据,为什么还还要用到队列?

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

    1 import queue
    2 
    3 q = queue.Queue()
    4 q.put('heiheihei')
    5 print(q.get())
    Queue
    1 q = queue.LifoQueue()
    2 q.put(1)
    3 q.put(2)
    4 q.put(3)
    5 print(q.get()) #堆栈
    LifoQueue
    1 q = queue.PriorityQueue()
    2 #数字越小,优先级就越高
    3 q.put((10,'bibibi'))
    4 q.put((100,'gugugu'))
    5 q.put((50,'lalala'))
    6 q.put((0,'xixixi'))
    7 print(q.get())
    PriorityQueue
  • 相关阅读:
    Math.floor,Math.ceil,Math.rint,Math.round用法
    double类型转换为int类型四舍五入工具类
    精度更高的double类型计算工具类(借助BigDecimal类型)
    Java中float和double转换的问题
    Java对数
    iPhone内存溢出——黑白苹果
    VBS猜数游戏
    HTML_1
    MySQL学习
    Python进制转换
  • 原文地址:https://www.cnblogs.com/spencerzhu/p/11354384.html
Copyright © 2011-2022 走看看