DAY 42 多线程锁机制

GIL(全局解释器锁)

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
这里写图片描述
python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
GIL原理图
这里写图片描述
计算密集型:结果肯定是100,因为每一次start结果就已经出来了,所以第二个线程肯定是通过调用第一个线程的count值进行计算的

def sub():
    global count

    '''线程的公共数据  下'''
    temp=count
    count=temp+1
    '''线程的公共数据  上'''

    time.sleep(2)
count=0

l=[]
for i in range(100):
    t=threading.Thread(target=sub,args=())
    t.start()  #每一次线程激活,申请一次gillock
    l.append(t)
for t in l:
    t.join()
print(count)

io密集型:当第一个线程开始start的时候,由于sleep了0.001秒,这0.001秒对于人而言很短,但是对于cpu而言,这0.001秒已经做了很多的事情了,在这里cpu做的事情就是或许已经start了100个线程,所以导致大多数的线程调用的count值还是0,即temp=0,只有少数的线程完成了count=temp+1的操作,所以输出的count结果不确定,可能是7、8、9,也可能是10几。

def sub():
    global count

    '''线程的公共数据  下'''
    temp=count
    time.sleep(0.001)    #大量的io操作
    count=temp+1
    '''线程的公共数据  上'''

    time.sleep(2)
count=0

l=[]
for i in range(100):
    t=threading.Thread(target=sub,args=())
    t.start()
    l.append(t)
for t in l:
    t.join()
print(count)

意以下的锁都是多线程提供的锁机制,与python解释器引入的gil概念无关

互斥锁(同步锁

互斥锁是用来解决上述的io密集型场景产生的计算错误,即目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。

def sub():
    global count
    lock.acquire()  #上锁,第一个线程如果申请到锁,会在执行公共数据的过程中持续阻塞后续线程
                    #即后续第二个或其他线程依次来了发现已经被上锁,只能等待第一个线程释放锁
                    #当第一个线程将锁释放,后续的线程会进行争抢

    '''线程的公共数据  下'''
    temp=count
    time.sleep(0.001)
    count=temp+1
    '''线程的公共数据  上'''

    lock.release()  #释放锁
    time.sleep(2)
count=0

l=[]
lock=threading.Lock()   #将锁内的代码串行化
for i in range(100):
    t=threading.Thread(target=sub,args=())
    t.start()
    l.append(t)
for t in l:
    t.join()
print(count)

死锁
保护不同的数据就应该加不同的锁。

所以当有多个互斥锁存在的时候,可能会导致死锁,死锁原理如下:

import threading
import time
def foo():
    lockA.acquire()
    print('func foo ClockA lock')
    lockB.acquire()
    print('func foo ClockB lock')
    lockB.release()
    lockA.release()

def bar():

    lockB.acquire()
    print('func bar ClockB lock')
    time.sleep(2)  # 模拟io或者其他操作,第一个线程执行到这,在这个时候,lockA会被第二个进程占用
                   # 所以第一个进程无法进行后续操作,只能等待lockA锁的释放
    lockA.acquire()
    print('func bar ClockA lock')
    lockB.release()
    lockA.release()

def run():
    foo()
    bar()

lockA=threading.Lock()
lockB=threading.Lock()
for i in range(10):
    t=threading.Thread(target=run,args=())
    t.start()

输出结果:只有四行,因为产生了死锁阻断了
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func foo ClockA lock

递归锁(重要)

解决死锁

import threading
import time
def foo():
    rlock.acquire()
    print('func foo ClockA lock')
    rlock.acquire()
    print('func foo ClockB lock')
    rlock.release()
    rlock.release()

def bar():
    rlock.acquire()
    print('func bar ClockB lock')
    time.sleep(2)
    rlock.acquire()
    print('func bar ClockA lock')
    rlock.release()
    rlock.release()


def run():
    foo()
    bar()

rlock=threading.RLock() #RLock本身有一个计数器,如果碰到acquire,那么计数器+1
                        #如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1

for i in range(10):
    t=threading.Thread(target=run,args=())
    t.start()

Semaphore(信号量)

实际上也是一种锁,该锁用于限制线程的并发量

以下代码在sleep两秒后会打印出100个ok

import threading
import time
def foo():
    time.sleep(2)
    print('ok')

for i in range(100):
    t=threading.Thread(target=foo,args=())
    t.start()

每2秒打印5次ok

import threading
import time
sem=threading.Semaphore(5)
def foo():
    sem.acquire()
    time.sleep(2)
    print('ok')
    sem.release()

for i in range(100):
    t=threading.Thread(target=foo,args=())
    t.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值