Python---线程的锁

1、同步锁

为了防止读取到脏数据,对临界资源进行加锁,将并行被迫改为串行。通过threading.Lock()方法创建一把锁。

acquire() 方法:只有一个线程能成功的获取锁,按先后顺序 其他线程只能等待。release() 方法:线程释放。这把锁不允许在同一线程中被多次acquire()。

import threading
import time

def check():
    global number
    lock.acquire()  # 对临界资源进行加锁
    temp = number
    time.sleep(0.01)
    number = temp-1
    lock.release()

l = []
number = 100
# 实例一把锁
lock = threading.Lock()
for i in range(100):
    t = threading.Thread(target=check)
    t.start()
    l.append(t)

for t in l:
    t.join()
print(number)

2、递归锁(解决死锁问题)

递归锁:  RLock允许在同一线程中被多次acquire()  加锁,其内部存在着一个计数器,只要计数器大于0就没人能拿到这把锁。
acquire()方法和release()方法必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的锁。
通过  rlock = threading.Rlock()   方法获得一把递归锁。

死锁:当一个或多个进程等待系统资源,而资源又被进程本身或其它进程占用时,就形成了死锁。

import time
import threading

class MyThread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        LockA.acquire()
        print("got Alock"+str(threading.current_thread()))
        time.sleep(1)
        LockB.acquire()
        print("got Block" + str(threading.current_thread()))
        time.sleep(0.)
        LockB.release()
        LockA.release()

    def actionB(self):
        LockB.acquire()
        print("got Block"+str(threading.current_thread()))
        time.sleep(1)
        LockA.acquire()
        print("got Alock" + str(threading.current_thread()))
        time.sleep(0.1)
        LockA.release()
        LockB.release()

if __name__ == '__main__':
    LockA = threading.Lock()
    LockB = threading.Lock()
    L = []
    for i in range(5):
        t = MyThread()
        t.start()
        L.append(t)
    for t in L:
        t.join()
    print('ending')



当一个线程执行完actionA方法以后,会继续执行actionB方法,此时并不会产生死锁。当第二个线程开始执行的时候,可以获得A锁,当获取B锁的时候,返现B锁已经被第一个线程占用,而第一个线程也在请求A锁。两个线程僵持不下所以发生死锁。当加上递归锁以后就解决此问题。

加上递归锁:

import time
import threading

class MyThread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        LockA.acquire()
        print("got Alock"+str(threading.current_thread()))
        time.sleep(1)
        LockA.acquire()
        print("got Block" + str(threading.current_thread()))
        time.sleep(0.)
        LockA.release()
        LockA.release()

    def actionB(self):
        LockA.acquire()
        print("got Block"+str(threading.current_thread()))
        time.sleep(1)
        LockA.acquire()
        print("got Alock" + str(threading.current_thread()))
        time.sleep(0.1)
        LockA.release()
        LockA.release()

if __name__ == '__main__':
#   获得一把锁
    LockA = threading.RLock()
    L = []
#   实例线程对象
    for i in range(5):
        t = MyThread()
        t.start()
        L.append(t)
    for t in L:
        t.join()
    print('ending')

当第一个线程执行完actionA方法以后再执行actionB方法会再次请求一把相同的锁,和其他线程一同竞争获得该锁。从而解决了此问题。

3、信号量

保证两个或多个关键代码段不被并发调用,也是一种锁。threading.Semaphore(size) 创建一个信号量。也有acquire()、release()方法和上面相同。

import threading
import time

def check():
    # 当进来一个变量,就会减一,一共有5把锁
    if semaphore.acquire():
        print(threading.current_thread())
        time.sleep(3)
        # 每释放一把锁就会加一
        semaphore.release()
#         最后显示会每三秒出5个结果

if __name__ == '__main__':
    L = []
    # 创建5个信号量,信号量也是一种锁
    semaphore = threading.Semaphore(5)
    for i in range(100):
        t = threading.Thread(target=check)
        t.start()
        L.append(t)
    for t in L:
        t.join()
    print('end')

4、同步对象Event

threading.event()创建一个同步对象,类似于信号量机制,只不过信号量为一。
wait()方法:没有设定标志位就会卡住,一旦被设定等同于pass。set()方法:设定标志位。clear()方法:清楚标志位。is_set()方法:是否设置。

import time
import threading

class Boss(threading.Thread):

    def run(self):
        print("加班加班")
        #  检验是否设置标志位
        print(event.is_set())
        #  设置标志位
        event.set()
        time.sleep(3)
        # 清空标志位
        event.clear()
        print('加班结束')
        event.set()

class Worker(threading.Thread):

    def run(self):
#       等待设置标志位,如果设置,此语句相当于pass
        event.wait()
        print("命苦啊")
        time.sleep(1)
        event.clear()
        event.wait()
        print("OH yeah")

if __name__ == '__main__':
    L = []
#   创建一个同步对象
    event = threading.Event()
    for i in range(5):
        L.append(Worker())
    L.append(Boss())
    for t in L:
        t.start()
    for t in L:
        t.join()
    print("end")

5、生产者消费者模型

(1):队列:线程队列一种数据结构,是多线程的、安全的。列表是线程不安全的。

            队列的模式:1、先进先出   2、先进后出   3、按优先级。

import queue

# 创建一个队列先进先出   
q = queue.Queue(3)

# q = queue.LifoQueue()   创建先进后出队列。(栈)later in first out
# q = queue.PriorityQueue()  创建一个按优先级顺序的队列

# 添加数据  当超过队列大小时,就会卡住put。后面如果加上false参数就会在满的时候报错。
q.put(1)
q.put("csdn")
q.put({"name":"2333"})
q.put("baba")

# print(q.qsize())   不是最大值,而是当前队列有多少个元素。 
# print(q.maxsize)   队列的最长度
# print(q.full())   队满
# print(q.empty())   队空

while 1:
    print("+++++++++")
#   取数据,没数据的时候会一直卡住,一直等到有添加值。(另外的一个线程可以继续添加值)。后面如果加上false参数就会在满的时候报错
    print(q.get())
    print("---------")

当前程序会卡在put()方法。

当使用优先级队列时在put()数据时一同写上优先级,数值越小优先级越高。

q.put([1,1])

(2)Producer-consumer problem:

task_done()方法:完成一项工作后向另一个线程发信号。join()方法:等到队列为空时,在执行别的操作。成对出现,否则没意义。 

import time, threading, queue, random

def producer(name):
    count = 0
    while count < 10:
        print("making....")
        # 随机数范围1--2
        time.sleep(random.randrange(3))
        q.put(count)
        print("{}做好了第{}个包子".format(name,count))
        count += 1
        # q.task_done()
        q.join()

def consumer(name):
    count = 0
    while count < 10:
        time.sleep(random.randrange(4))
        print('waiting')
        # q.join()     没有信号会卡住,有人给信号就往下执行
        data = q.get()
        q.task_done()
        print("{}正在吃第{}个包子".format(name,data))
        count += 1

if __name__ == '__main__':
    q = queue.Queue()
    t1 = threading.Thread(target=producer,args=('A君',))
    t2 = threading.Thread(target=consumer,args=('B君',))
    t3 = threading.Thread(target=consumer,args=('C君',))
    t4 = threading.Thread(target=consumer,args=('D君',))
    t1.start()
    t2.start()
    t3.start()
    t4.start()

task_done : 用于 get 的后续调用.告诉队列任务处理完成.当然你也可以不调用get。

join:阻塞操作,直到队列所有的任务都处理,换句话说,当队列里面没有东西的时候才会向下执行,否则会阻塞。也就是说:往里 put 几次,就要调用task_done几次。然而task_done()并不会使队列长度-1,而是会向队列发送信号,真正使队列长度减一的是get()操作。

put队列完成的时候千万不能用task_done(),否则会报错,因为该方法仅仅表示get成功后,执行的一个标记。

猜你喜欢

转载自blog.csdn.net/weixin_41678001/article/details/84487536
今日推荐