多线程编程之同步锁

535 阅读3分钟

1 基本介绍

Lock锁的称呼有很多,如:同步锁、互斥锁。

互斥锁指的是某一资源同一时刻仅能有一个访问者对其进行访问,具有唯一性和排他性。但是互斥锁无法限制访问者对资源的访问顺序,即访问是无序的。

同步锁是在互斥的基础上通过其他机制实现访问者对资源的有序访问。同步已经实现了互斥,是互斥的一种更为复杂的实现,因为它在互斥的基础上实现了有序访问的特点。

2 常用方法

同步锁相关的方法如下:

方法描述
threading.Lock()返回一个同步锁对象
threading.Lock().acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程,默认失效时间为1秒
threading.Lock().release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行
threading.Lock().locked()判断该锁对象是否处于上锁状态,返回一个布尔值

3 使用示例

同步锁一次只能放行一个线程,一个被加锁的线程在运行时不会将执行权交出去。只有当该线程被解锁时才会将执行权通过调度系统交给其他线程。

示例代码如下:

import threading
import time
import random


class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5

    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count

    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1


db = TicketDB()
lock = threading.Lock()


def thread_body():
    global db, lock
    while True:
        lock.acquire()
        curr_ticket_count = db.get_ticket_count()
        if curr_ticket_count > 0:
            db.sell_ticket()
        else:
            lock.release()
            break
        lock.release()
        time.sleep(1)


def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()


if __name__ == '__main__':
    main()

result:

Thread-1网点,已经售出第5张票
Thread-2网点,已经售出第4张票
Thread-1网点,已经售出第3张票
Thread-2网点,已经售出第2张票
Thread-1网点,已经售出第1张票

4 死锁问题

对于同步锁来说,一次acquire()必须对应一次release(),不能连续重复使用多次acquire()后再重复使用多次release()d。这样会引起死锁造成程序的阻塞。

比如下面的例子

import threading
import time
import random


class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5

    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count

    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1


db = TicketDB()
lock = threading.Lock()


def thread_body():
    global db, lock
    while True:
        lock.acquire()
        lock.acquire()
        lock.acquire()
        curr_ticket_count = db.get_ticket_count()
        if curr_ticket_count > 0:
            db.sell_ticket()
        else:
            lock.release()
            lock.release()
            lock.release()
            break
        lock.release()
        lock.release()
        lock.release()

        time.sleep(1)


def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()


if __name__ == '__main__':
    main()

5 with语句

threading.Lock()对象中实现了__enter__()方法与__exit__()方法,所以我们可以使用with语句进行上下文管理形式的加锁解锁操作。

示例代码如下:

import threading
import time
import random


class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5

    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count

    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1


db = TicketDB()
lock = threading.Lock()


def thread_body():
    global db, lock
    while True:
        with lock:
            curr_ticket_count = db.get_ticket_count()
            if curr_ticket_count > 0:
                db.sell_ticket()
            else:
                break
            time.sleep(1)


def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()


if __name__ == '__main__':
    main()

result:

Thread-1网点,已经售出第5张票
Thread-1网点,已经售出第4张票
Thread-2网点,已经售出第3张票
Thread-2网点,已经售出第2张票
Thread-1网点,已经售出第1张票