同步锁 死锁与递归锁 信号量 线程queue event事件

二个需要注意的点:
1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock任然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来

2 join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

GIL  Lock

锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

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

GIL与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

  线程1抢到GIL锁,拿到执行权限,开始执行,然后加一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock....这就导致了串行运行的效果

  既然是串行,那我们执行

  t1.start()

  t1.join()

  t2.start()

  t2.join()

  这也是串行执行啊,为何要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。

from threading import Thread,Lock
import time
mutex=Lock()

n=100

def task():
    global n
    # mutex.acquire()
    tem=n
    time.sleep(0.1)
    n=tem-1
    # mutex.release()

l_list=[]
for i in range(100):
    t=Thread(target=task)
    t.start()
    l_list.append(t)

for t in l_list:
    t.join()

    # print(n)  #每结束一个线程都打印一次100个99
print(n)    #打印的是结果 99
gil保证的是解释器代码的数据安全 让每个线程实现并发的效果

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其他线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,在调用release方法释放锁:

import threading

R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()
from threading import Thread,Lock
import time
mutex=Lock()

n=100

def task():
    global n
    mutex.acquire()
    tem=n
    time.sleep(0.1)
    n=tem-1
    mutex.release()

l_list=[]
for i in range(100):
    t=Thread(target=task)
    t.start()
    l_list.append(t)

for t in l_list:
    t.join()

    print(n)  #每结束一个线程就打印一次99-0
print(n)   #0最后结果 由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

GIL锁与互斥锁综合分析!!!

分析:

    1    100个线程去抢GIL锁,即执行权限
    2    肯定有一个线程先抢到GIL(暂且称之为线程1),然后开始执行一旦执行就会拿到lock.acquire()
    3    极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
    4    直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程在重复2 3 4 的过程

互斥锁与join的区别!!!

#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    global n
    print('%s is running' %current_thread().getName())
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''


#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    #未加锁的代码并发运行
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    #加锁的代码串行运行
    lock.acquire()
    temp=n
    time.sleep(0.5)
    n=temp-1
    lock.release()

if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

#有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
#没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
#start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
#单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''

互斥锁与join的区别(重点!!!)
互斥锁与join的区别

死锁现象与递归锁

 进程也有死锁与递归锁  

自定义锁一次acquire必须对应一次release,不能连续acquire 

递归锁可连续的acquire,没acquire一次计数加一,针对的是第一个抢到我的人

所谓死锁:是指两个或两个以上的进程或者线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称之死锁进程,如下就是死锁

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()
        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()
        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''
死锁

解决方法,递归锁,在python中为了支持在同一线程中多次请求统一资源,python童工了可重如锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了一个acquire的次数,从而使得资源可以被多次require(需求),直到一个线程中的所有acquire都被release,其他的线程才能获得资源。

递归锁

 

信号量Semaphore

同进程的一样

Semaphore管理一个内置的计数器

每当调用acquire()时内置计数器-1

没当release()时内置计时器+1

计数器不能小于0当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

from threading import Thread,Semaphore
import time
import random
sm = Semaphore(5)  # 五个厕所五把锁
# 跟你普通的互斥锁区别在于,普通的互斥锁是独立卫生间,所有人抢一把锁
# 信号量 公共卫生间 有多个坑,所有人抢多把锁



def task(name):
    sm.acquire()
    print('%s正在蹲坑'%name)
    # 模拟蹲坑耗时
    time.sleep(random.randint(1,5))
    sm.release()


if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task,args=('伞兵%s号'%i,))
        t.start()
公共厕所

 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

from threading import Event,Thread
import time
import random


event = Event()


def light():
    print('红灯亮着!')
    time.sleep(3)
    event.set()  # 解除阻塞,给我的event发了一个信号
    print('绿灯亮了!')


def car(i):
    print('%s 正在等红灯了'%i)
    event.wait()  # 阻塞
    print('%s 加油门飙车了'%i)

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


for i in range(5):
    t = Thread(target=car,args=(i,))
    t.start()

线程queue 

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

queue.Queue 先进先出

queue.LifoQueue 先进后出

queue.priorityQueue 设置优先级

import queue

q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

1
2
3

import queue
q = queue.LifoQueue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get())

4
3
2
1

import queue
q = queue.PriorityQueue()
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get())

(-1, 'b')
(10, 'a')
(100, 'c')

猜你喜欢

转载自www.cnblogs.com/lakei/p/10834262.html
今日推荐