Python多线程锁:使用Lock实现线程同步的方法

一、Lock的作用及使用方法

在多线程中,为了防止数据错乱或者变量冲突,需要对线程进行同步。Python提供了多种同步方式,其中Lock是最基本的同步方法之一。

Lock对象可以看作是一个标志,表示只有当获取该标志的线程释放该标志之后,其他线程才能获取它。这样就可以保证同一时间只有一个线程访问共享资源。

使用Lock对象时,需要获取该对象的锁定,并在需要访问共享资源的代码块前调用acquire()方法,使用完后要利用release()方法释放锁定。

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, thread_num, lock):
        threading.Thread.__init__(self)
        self.thread_num = thread_num
        self.lock = lock

    def run(self):
        print("Thread {} start".format(self.thread_num))
        self.lock.acquire()
        print("Thread {} acquired lock".format(self.thread_num))
        time.sleep(1)
        self.lock.release()
        print("Thread {} released lock".format(self.thread_num))

if __name__ == '__main__':
    lock = threading.Lock()
    threads = []
    for i in range(5):
        threads.append(MyThread(i, lock))
    for t in threads:
        t.start()
    for t in threads:
        t.join()

以上代码展示了Lock对象的使用方法。在这个例子中,创建了5个线程MyThread,每个线程都需要获取同一个Lock对象的锁定,在获取锁定后暂停1秒钟,然后释放锁定。

二、Lock的带计数器版本RLock

在Python中,还有一种类似Lock的带计数器版本,叫做RLock(R=Reentrant, 可重入)。相对于Lock而言,RLock多了一个计数器。同一个线程在多次调用acquire()方法后,只有在计数器为0的情况下,其他线程才能获取该锁。

可以通过调用Lock对象的方法获得RLock对象,就像下面这样:

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, thread_num, lock):
        threading.Thread.__init__(self)
        self.thread_num = thread_num
        self.lock = lock

    def run(self):
        print("Thread {} start".format(self.thread_num))
        self.lock.acquire()
        print("Thread {} acquired lock".format(self.thread_num))
        time.sleep(1)
        self.lock.acquire()
        print("Thread {} acquired lock again".format(self.thread_num))
        time.sleep(1)
        self.lock.release()
        print("Thread {} released lock".format(self.thread_num))
        time.sleep(1)
        self.lock.release()
        print("Thread {} released lock again".format(self.thread_num))

if __name__ == '__main__':
    lock = threading.RLock()
    threads = []
    for i in range(5):
        threads.append(MyThread(i, lock))
    for t in threads:
        t.start()
    for t in threads:
        t.join()

在上述的代码中,MyThread线程在第一次获取RLock对象锁定后,调用了acquire()方法增加了计数器的值,然后又调用了一次acquire()方法,再次获取该锁定,计数器的值增加再次。通过两次计数器值的增加,就达到了可重入的目的。

三、使用Lock对象避免死锁

Lock对象虽然可以很好保证同步,但是如果在使用不当的情况下,可能会出现死锁的问题。比如,线程A获取了锁1,想要获取锁2;线程B获取了锁2,想要获取锁1,这时就出现了死锁。

避免死锁的方法是,在获取所有的锁前,先获取其中一个,如果没有获得,则释放已经获得的锁,等待一段时间后重试。这段等待的时间,可以通过调整参数来优化。

下面是一个典型的死锁示例:

import threading

def worker1(lock1, lock2):
    lock1.acquire()
    print("Worker1 acquired lock1")
    lock2.acquire()
    print("Worker1 acquired lock2")
    lock1.release()
    print("Worker1 released lock1")
    lock2.release()
    print("Worker1 released lock2")

def worker2(lock1, lock2):
    lock2.acquire()
    print("Worker2 acquired lock2")
    lock1.acquire()
    print("Worker2 acquired lock1")
    lock2.release()
    print("Worker2 released lock2")
    lock1.release()
    print("Worker2 released lock1")

if __name__ == '__main__':
    lock1 = threading.Lock()
    lock2 = threading.Lock()

    t1 = threading.Thread(target=worker1, args=(lock1, lock2))
    t2 = threading.Thread(target=worker2, args=(lock1, lock2))

    t1.start()
    t2.start()
    t1.join()
    t2.join()

以上代码演示了死锁。在该示例中,worker1和worker2都需要获取两个Lock对象的锁定,如果两个线程在获取锁定的顺序不同,则可能会出现死锁。在这种情况下,就需要按照特定的顺序获取锁定,避免死锁。比如下面的程序:

import threading
import time

def worker1(lock1, lock2):
    print("Worker1 waiting for lock1")
    lock1.acquire()
    print("Worker1 acquired lock1")
    time.sleep(1)
    while not lock2.acquire(blocking=False):
        print("Worker1 waiting for lock2")
        time.sleep(1)
    print("Worker1 acquired lock2")
    lock1.release()
    print("Worker1 released lock1")
    lock2.release()
    print("Worker1 released lock2")

def worker2(lock1, lock2):
    print("Worker2 waiting for lock1")
    lock2.acquire()
    print("Worker2 acquired lock2")
    time.sleep(1)
    while not lock1.acquire(blocking=False):
        print("Worker2 waiting for lock1")
        time.sleep(1)
    print("Worker2 acquired lock1")
    lock2.release()
    print("Worker2 released lock2")
    lock1.release()
    print("Worker2 released lock1")

if __name__ == '__main__':
    lock1 = threading.Lock()
    lock2 = threading.Lock()

    t1 = threading.Thread(target=worker1, args=(lock1, lock2))
    t2 = threading.Thread(target=worker2, args=(lock1, lock2))

    t1.start()
    t2.start()
    t1.join()
    t2.join()

在上述的程序中,不管worker1和worker2的获取锁的顺序如何,先获取的线程会最终释放自己获取的锁,再次获取另一个锁。同时如果不成功获取另一个锁,则会等待一段时间后重试获取锁定,这样就可以避免死锁。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/279479.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2024-12-20 15:04
下一篇 2024-12-20 15:04

相关推荐

  • 如何查看Anaconda中Python路径

    对Anaconda中Python路径即conda环境的查看进行详细的阐述。 一、使用命令行查看 1、在Windows系统中,可以使用命令提示符(cmd)或者Anaconda Pro…

    编程 2025-04-29
  • Python中引入上一级目录中函数

    Python中经常需要调用其他文件夹中的模块或函数,其中一个常见的操作是引入上一级目录中的函数。在此,我们将从多个角度详细解释如何在Python中引入上一级目录的函数。 一、加入环…

    编程 2025-04-29
  • Python计算阳历日期对应周几

    本文介绍如何通过Python计算任意阳历日期对应周几。 一、获取日期 获取日期可以通过Python内置的模块datetime实现,示例代码如下: from datetime imp…

    编程 2025-04-29
  • Python周杰伦代码用法介绍

    本文将从多个方面对Python周杰伦代码进行详细的阐述。 一、代码介绍 from urllib.request import urlopen from bs4 import Bea…

    编程 2025-04-29
  • Python列表中负数的个数

    Python列表是一个有序的集合,可以存储多个不同类型的元素。而负数是指小于0的整数。在Python列表中,我们想要找到负数的个数,可以通过以下几个方面进行实现。 一、使用循环遍历…

    编程 2025-04-29
  • Python程序需要编译才能执行

    Python 被广泛应用于数据分析、人工智能、科学计算等领域,它的灵活性和简单易学的性质使得越来越多的人喜欢使用 Python 进行编程。然而,在 Python 中程序执行的方式不…

    编程 2025-04-29
  • Python清华镜像下载

    Python清华镜像是一个高质量的Python开发资源镜像站,提供了Python及其相关的开发工具、框架和文档的下载服务。本文将从以下几个方面对Python清华镜像下载进行详细的阐…

    编程 2025-04-29
  • python强行终止程序快捷键

    本文将从多个方面对python强行终止程序快捷键进行详细阐述,并提供相应代码示例。 一、Ctrl+C快捷键 Ctrl+C快捷键是在终端中经常用来强行终止运行的程序。当你在终端中运行…

    编程 2025-04-29
  • 蝴蝶优化算法Python版

    蝴蝶优化算法是一种基于仿生学的优化算法,模仿自然界中的蝴蝶进行搜索。它可以应用于多个领域的优化问题,包括数学优化、工程问题、机器学习等。本文将从多个方面对蝴蝶优化算法Python版…

    编程 2025-04-29
  • Python字典去重复工具

    使用Python语言编写字典去重复工具,可帮助用户快速去重复。 一、字典去重复工具的需求 在使用Python编写程序时,我们经常需要处理数据文件,其中包含了大量的重复数据。为了方便…

    编程 2025-04-29

发表回复

登录后才能评论