简述python多线程中的互斥锁

        编程中,为了保证共享数据操作的完整性,引入了互斥锁的概念。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在python中由于多线程的存在,并且对全局变量作用时有可能产生全局变量紊乱问题,所以也加入了同步和互斥等方法,但是我们主要讲解互斥锁:

如下代码定义了一个my_num的全局变量,而函数sum1和函数sum2都执行对my_num加1操作,并且都执行100万次+1,在下面的代码里,我们创建了两个分别执行两个函数的线程,然后调用start()启动线程

import threading
import time

# 定义全局变量
my_num = 0


# 循环给全局变量每次加上1
def sum1():
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum1:", my_num)


# 循环给全局变量每次加上1
def sum2():
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum2:", my_num)


if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=sum1)
    second_thread = threading.Thread(target=sum2)

    # 启动线程执行对应的任务
    first_thread.start()# 主线程延时1.5秒
    # time.sleep(1.5)
    # 主线程等待第一个线程执行完成以后,代码再继续往下执行,启动第二个线程执行任务
    # first_thread.join()  # 线程同步: 按照预先定义好的顺序一个任务执行完成另外一个任务再执行
    # print("第一个线程执行完了")
    second_thread.start()

得到执行结果如下:

sum1: 1239023
sum2: 1341576

显然这结果不是我们想要的,我们希望的结果是函数1和函数2都执行100万次加一,最终结果是想变成200万,当然,出现这样的结果,我们也很容易想到原因是:当全局变量my_num 刚好等于1000(某些数字时,这里只是假设)的时候,两个线程同时拿到了my_num的值,各自对他进行了+1操作,此时明显全局my_num的到的只是加一而不是+2操作(不往细了说)

明显看出:在一个进程内非常方便多个线程对全局变量的数据共享,而对全局变量进行随意修改可能会造成全局变量的混乱,即多个线程同时对一个全局变量操作,会出现资源竞争问题,从而数据结果不正确。

同样,下面的代码中我们加入了互斥锁

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

import threading

# 定义全局变量
my_num = 0

# 创建互斥锁
lock = threading.Lock()

# def show():
#     print("哈哈")
# # 变量保存的是函数在内存中的地址,保存的函数引用
# a = show
# a()


# 循环给全局变量每次加上1
def sum1():
    # 上锁
    lock.acquire()
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum1:", my_num)
    # 释放锁
    lock.release()


# 循环给全局变量每次加上1
def sum2():
    # 上锁
    lock.acquire()
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum2:", my_num)
    # 释放锁
    lock.release()

if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=sum1)
    second_thread = threading.Thread(target=sum2)

    # 启动线程执行对应的任务
    first_thread.start()
    second_thread.start()

    # 互斥锁:能保证统一时刻只有一个线程执行,那个线程抢到这个互斥锁我们决定不了,但是能保证数据最终不会有问题
    # 提示:加上互斥锁把多任务瞬间变成单任务,执行效率会下降。

可以看到最后的结果,加入互斥锁后,其结果与预期相符。

得到结果:

sum1: 1000000
sum2: 2000000

锁的好处:

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁



<think> 首先,用户的问题是:“我想查找两个进程间同时访问一把互斥锁的方法 请问两个进程同时访问一把互斥锁的方法”。用户引用了几个关于Linux线程、互斥锁Python多进程互斥锁的引用。 从引用中提取关键信息: - 引用[1]: 讨论Linux线程与互斥锁,强调互斥锁在同一时刻只有一个线程能持有,其他线程会被挂起直到释放。 - 引用[2]: 类似,解释互斥锁在Linux多线程中的作用,只有一个线程能上,其他等待。 - 引用[3]: Python多进程中的互斥锁,通过multiprocessing.Lock实现并发控制,保证数据安全。 - 引用[4]: Python多进程互斥锁示例,使用Lock确保一次只有一个进程访问资源。 - 引用[5]: 另一个Python多进程互斥锁示例,模拟上厕所,用于序列化访问。 用户的问题是“两个进程间同时访问一把互斥锁的方法”。互斥锁的本质是防止同时访问共享资源;它被设计为同一时间只有一个实体(线程或进程)能持有。"同时访问"在这里可能意味着如何让多个进程尝试访问同一把,但互斥锁的设计就是不允许同时访问;当一个进程持有时,另一个进程必须等待。 所以,用户可能在问:如何在两个进程间共享互斥锁,以实现同步访问共享资源。或者是问互斥锁的机制如何工作。 回顾用户的问题:"两个进程间同时访问一把互斥锁的方法"。在并发编程中,"访问"互斥锁通常意味着尝试获取互斥锁的方法是:acquire() 和 release()。 从引用中看,互斥锁的使用步骤是: 1. 创建一个互斥锁对象(在进程间共享)。 2. 在访问共享资源前,调用 acquire() 获取。 3. 如果已被占用,进程阻塞或等待。 4. 访问资源后,调用 release() 释放。 在Python的multiprocessing模块中,Lock对象是专门设计用于进程间的互斥访问。引用[3]、[4]、[5]都展示了如何使用multiprocessing.Lock。 例如,在引用[3]中: ```python from multiprocessing import Process, Lock import os, time def work(lock): lock.acquire() #加 print('%s is running' % os.getpid()) time.sleep(2) print('%s is done' % os.getpid()) lock.release() #释放 if __name__ == '__main__': lock = Lock() for i in range(3): p = Process(target=work, args=(lock,)) p.start() ``` 在这里,被多个进程共享,每个进程尝试acquire时,只有一个能持有它。 所以,回答用户的问题:方法是使用进程间共享的互斥锁对象,如Python的multiprocessing.Lock,并在代码中调用acquire()和release()方法。 用户提到“两个进程间同时访问一把互斥锁”,但互斥锁不允许同时访问;它序列化访问。所以,我需要澄清:方法是通过来协调访问,而不是同时访问。 用户可能误以为“同时访问”是可行的,但互斥锁的目的是防止同时访问。 现在,系统级指令: - 所有行内数学表达式用$...$格式(但这里可能没有数学)。 - 独立公式用$$...$$(同样,可能不需要)。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 引用用户提供的引用:[^1], [^2], [^3], [^4], [^5]。 结构回答: - 解释互斥锁的基本原理。 - 描述如何在进程间共享互斥锁(特别是在Python中)。 - 提供代码示例。 - 生成相关问题。 确保回答真实可靠,基于引用。 引用内容: - [^1]: 互斥锁在同一时刻只能有一个线程掌握。 - [^2]: 类似描述。 - [^3]: Python中使用multiprocessing.Lock。 - [^4]: 另一个Python示例。 - [^5]: 模拟上厕所。 所以,在回答中,我可以引用这些来说明。 最终回答结构: 1. 简述互斥锁的作用。 2. 解释两个进程访问互斥锁的方法:通过创建共享对象,并在进程中调用acquire和release。 3. 提供Python代码示例。 4. 自然地添加引用标识。 5. 生成相关问题。 </think> ### 两个进程间共享互斥锁的方法 在并发编程中,互斥锁(Mutex)用于协调多个进程或线程对共享资源的访问,确保同一时间只有一个实体能持有,从而避免数据竞争和不一致性。互斥锁的本质是序列化访问,而非允许"同时访问"。当两个进程尝试访问同一把互斥锁时,方法是通过的获取(acquire)和释放(release)机制来实现同步控制。以下是基于您提供的引用和标准并发编程实践的原理和方法总结: #### 1. **互斥锁的基本原理** - 互斥锁只有两种状态:上和解。当一个进程持有时,其他进程尝试获取会被阻塞(挂起),直到被释放[^1][^2]。 - 在进程间共享互斥锁时,对象需要在创建时能被多个进程访问(例如,通过共享内存或IPC机制)。这确保了所有进程引用同一个实例。 - 的核心操作: - `acquire()`:尝试获取。如果已被占用,进程阻塞(等待)。 - `release()`:释放,允许其他进程获取。 - 互斥锁将并发访问改为串行执行,虽然降低了效率,但保证了数据安全[^3]。 #### 2. **实现两个进程共享互斥锁的方法(以Python为例)** 在Python中,`multiprocessing.Lock`类专门用于进程间互斥锁对象在父进程中创建后,可传递给子进程作为参数,确保所有进程共享同一个。以下是具体步骤和代码示例: - **步骤**: 1. 在父进程中创建互斥锁对象(`lock = Lock()`)。 2. 将对象作为参数传递给子进程(`Process(target=function, args=(lock,))`)。 3. 在子进程函数中,使用`acquire()`获取前访问共享资源,完成后调用`release()`释放。 4. 当一个进程持有时,另一个进程尝试`acquire()`会被自动阻塞,直到释放。 - **代码示例**(基于引用[^3][^4]的简化): ```python from multiprocessing import Process, Lock import time import os import random # 共享资源访问函数 def shared_task(process_id, lock): lock.acquire() # 尝试获取 print(f'进程 {process_id} (PID: {os.getpid()}) 开始访问共享资源') time.sleep(random.randint(1, 2)) # 模拟资源访问时间 print(f'进程 {process_id} (PID: {os.getpid()}) 结束访问并释放') lock.release() # 释放 if __name__ == '__main__': # 创建互斥锁对象 lock = Lock() # 创建并启动两个进程 p1 = Process(target=shared_task, args=('进程1', lock)) p2 = Process(target=shared_task, args=('进程2', lock)) p1.start() p2.start() p1.join() p2.join() print('主进程结束') ``` **输出示例**: ``` 进程 进程1 (PID: 1234) 开始访问共享资源 进程 进程1 (PID: 1234) 结束访问并释放 进程 进程2 (PID: 5678) 开始访问共享资源 进程 进程2 (PID: 5678) 结束访问并释放 主进程结束 ``` - **解释**: - 进程1先acquire,访问资源时进程2被阻塞。 - 进程1release后,进程2才acquire成功并访问资源。 - 这确保了共享资源(如文件、内存区)的访问是串行的,防止数据错乱[^3][^4]。 #### 3. **关键注意事项** - **的共享机制**:在Python中,`multiprocessing.Lock`使用操作系统级原语(如POSIX信号量),确保在跨进程间有效共享。类似机制在其他语言如C/C++中通过pthread或semaphore实现[^1][^2]。 - **避免死**:确保每个`acquire()`都有对应的`release()`;否则,未释放会导致其他进程永久阻塞。 - **性能影响**:互斥锁会降低并发效率,但这是保证数据安全的必要代价。在高竞争场景下,可考虑优化如非阻塞或读写[^2][^3]。 - **跨进程兼容性**:对象必须在所有进程中引用同一个实例(在父进程创建并传递),否则无法实现同步[^4][^5]。 总之,两个进程访问同一互斥锁的正确方法是通过的获取和释放序列来控制共享资源访问,而非真正意义上的"同时访问"。这在Linux和Python中通过标准库(如`multiprocessing.Lock`)高效实现[^1][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值