GIL全局解释器锁、递归锁、

一、全局解释器锁GIL:

  官方的解释:掌握概念为主

"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe.
"""

 

 (1):python代码的执行由python虚拟机(解释器)来控制,加锁是为了保证同一时刻只有一个线程再运行

 

用来阻止同一个进程下的多个线程的同时执行(一个进程内多个进程无法实现并行,但可以实现并发)

 

 (2):python解释器有很多种,最常见的就是cpython解释器,内部是由c语言编写的;

GIL本质也是一把互斥锁:将并发变成串行,虽然牺牲了执行效率,但保证了数据的安全性;

 

GIL的存在是因为Cpython解释器的内存管理不是线程安全的

垃圾回收机制:

  1:引用计数(没有被定义使用的)

  2:标记清除()

  3:分带回收(青春带,老年代)

 

python的多线程没法利用多核优势  是不是就是没有用了?
  看情况讨论,而且肯定是有用的

(3):研究python的多线程是否有用需要分情况讨论(分计算密集型和IO密集型)
四个任务 计算密集型的  10s
单核情况下
开线程更省资源
多核情况下
开进程 10s
开线程 40s

四个任务 IO密集型的
单核情况下
开线程更节省资源
多核情况下
开线程更节省资源

计算密集型:
1、特点:要进行大量的计算,消耗CPU资源。比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力
# 计算密集型
from multiprocessing import Process
from threading  import Thread
import  os,time

def work ():
    res = 0
    for i in range(10000000):
        res *= 1
if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) # 本机电脑的核数
    start = time.time()
    for i in range(6):
        # p= Thread(target=work)  # 开线程  run time is 0.10372185707092285
        p=Process(target=work)    # 开进程   run time is 0.8038477897644043
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s'%(stop-start))

  IO密集型:

  特点:CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)

# IO密集型

from  multiprocessing import Process
from threading  import  Thread
import  threading
import os
import time

def work():
    time.sleep(2)


if __name__ == '__main__':
    l=[]
    print(os.cpu_count())
    start = time.time()
    for i in range(40):
        # p = Process(target=work) # 进程耗时 run time is 2.7755727767944336
        p=Thread(target=work ) #线程耗时  2.005638360977173
        l.append(p)
        p.start()
    for p in l :
        p.join()
        stop = time.time()

    print(' run time is %s'%(stop-start))

ps:数据密集(Data-Intensive)

 

二、GIL与普通的互斥锁:

 

from threading import Thread
import time

n=100
def task():
    global n
    tmp = n
    time.sleep(2) # IO阻塞等待的 相当于一把锁
    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/0

"""time.sleep(2) # IO操作阻塞等待的 相当于一把锁,
100个中随机第一个抢到锁后,后面的就就进不去了,把这个条件取消掉就能全部依次取完,结果为0 """

三、死锁与递归锁

  所谓的死锁:就是指两个或两个以上的进程或线程在执行的时候,因为争夺资源而造成一种互相等待的现象,再也无法执行下去

  解决死锁的办法就是利用递归锁(RLock),把Lock 变成RLock

递归锁:就是可以支持在同一线程中多次请求同一资源,简单的来说就是两者共用同一把锁,当你加锁后释放锁时,另一个同时也可以访问资源并循环的进行加锁(acquire)和 (释放锁)release,直到结束访问为止

from  threading import Thread,Lock,RLock,current_thread
import time

from threading import Thread,Lock,current_thread,RLock
import time
"""
Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢
"""
# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()  # A B现在是同一把锁

class MyThread(Thread):
    def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)

    def func2(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 = MyThread()
    t.start()

GIL全局解释器锁、递归锁、

关于加锁问题:自己千万不能轻易的处理锁的问题,容易造成死锁现象

四、信号量

  信号量 某一段代码,同一时间,只能被N个进程使用

"""
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)
"""
from threading import Semaphore,Thread
import time
import random


sm = Semaphore(5)  # 造了一个含有五个的坑位的公共厕所

def task(name):
    sm.acquire()
    print('%s占了一个坑位'%name)
    time.sleep(random.randint(1,3)) #随机阻塞
    sm.release()

for i in range(40):
    t = Thread(target=task,args=(i,))
    t.start()
四十个循环随机产生枪锁和释放锁

 

五、event事件:

  一般用在一个子线程等待另一个子线程的时候,与进程的.join反法,主进程等子进程运行完毕后才执行

 

from threading import Thread,Event #  事件模块 

Event几种方法:

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

 

等红绿灯事件:

from threading import Event,Thread
import time

# 先生成一个event对象
e = Event()

def light():
    print('红灯正亮着')
    time.sleep(3)
    e.set()  # 发信号
    print('绿灯亮了')

def car(name):
    print('%s正在等红灯'%name)
    e.wait()  # 等待信号
    print('%s加油门飙车了'%name)

t = Thread(target=light)
t.start()

for i in range(10):
    t = Thread(target=car,args=('伞兵%s'%i,))
    t.start()

>>>>:
伞兵8正在等红灯
伞兵9正在等红灯
绿灯亮了伞兵0加油门飙车了
伞兵3加油门飙车了
伞兵4加油门飙车了
伞兵8加油门飙车了
伞兵1加油门飙车了
伞兵5加油门飙车了
伞兵9加油门飙车了
伞兵6加油门飙车了伞兵7加油门飙车了


伞兵2加油门飙车了
 

六、线程Queue(对列)

  queue队列 :使用import queue,用法与进程Queue一样

  

class queue.Queue(maxsize=0) #先进先出

class queue.LifoQueue(maxsize=0) #last in fisrt out 后进先出

class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列  优先级对列

结果(数字越小优先级越高,优先级高的优先出队):

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

 同一个进程下的多个线程本来就是数据共享 为什么还需要队列呢?

  因为队列是管道+锁的形式,使用队列就不需要自己手动操作锁的问题了,

锁操作极易造成死锁的现象

 

 

 









  

 

上一篇:GIL全局解释锁.死锁与递归锁


下一篇:线程中的锁的相关问题