python线程锁和进程锁

       在python的多线程和多进程中,当我们需要对多线程或多进程的共享资源或对象进行修改操作时,往往会出现因cpu随机调度而导致结果和我们预期不一致的问题,这时就需要对线程或者进程加锁,以保证一个线程或进程在对共享对象进行修改时,其他的线程或进程无法访问这个对象,直至获取锁的线程的操作执行完毕后释放锁。所以,锁在多线程和多进程中起到一个同步的作用,以保护每个线程和进程必要操作的完整执行。

       python中,多线程和多进程的异同点主要在两大处:1、由于GIL机制,多线程只能实现并发,但多进程可以实现并行;2、同一个进程中的多线程是共享内存的,线程没有独立的内存,但是进程有独立的内存,所以多进程中的每个子进程是有自己的独立内存的,所以在多进程中,其之间的同名变量并不会冲突,是独立的。第一点异同往往决定我们的任务时该使用多进程还是多线程,如果是I/O密集型任务,考虑多线程,如果是计算密集型任务,考虑多进程;第二点异同会引出本文的重点,即两者在加锁的方式上是不一样的。

       首先讲一下加锁的机制,其是如何实现线程或进程保护的。这个实现的大致过程为:首先在需要同步的代码块前面加上lock.acquire()语句,表示需要先成功获取该锁,才能继续执行下面的代码,然后在需要同步的代码块后面加上lock.release()语句,表示释放该锁。所以,如果当一个线程或进程获取该锁,而且该锁没有被释放的话,那么其他的线程或进程是无法成功获取该锁的,从而也就没法执行下面的同步代码块,从而起到保护作用,直至释放该锁,其他的线程或进程才可以成功获取该锁,然后继续执行下面的代码块。通过上述,一个明显的基本前提是,不同线程或进程面对的锁必须是同一把锁,即同步代码块前后的lock对象必须是同一个,不然如果每个线程或进程有自己不同的锁,那么这个锁也就自然起不到保护作用了。

       由于多线程共享内存,所以我们只要在该进程中创建一个锁,然后锁定相应的代码块即可,因为内存的共享,使得不同子线程面对的就是同一把锁,如下代码所示。这样可以使得每个线程对x进行修改后,x最后依然保持原值。

from threading import Thread,Lock

lock=Lock()
x=1
def f(x):
    global x
    lock.acquire()
    x+=1
    x-=1
    lock.release()

if __name=='__main__':
    t1=Thread(target=f,args=(1,))
    t2=Thread(target=f,args=(2,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

       但是在多进程中,由于每个子进程都会有自己的独立内存,所以如果按以上方式创建进程,那么实际上在函数f中的lock对象会根据LEGB原则读取到外面的lock对象,也就是说lock=Lock()这条语句也会被包括进每个子进程中,从而每个子进程不仅会在内存创建函数f,也会创建自己独立的锁对象,这样违背了一锁原则,从而无法起到同步作用。所以多进程中正确的加锁方式应该是先在主进程中创建一个锁,然后以参数的形式传给每个进程,这样相当于不同的子进程面对的是同一个锁。虽然实际上,每个子进程还是会在自己的内存中保存自己的锁对象,即如果获取每个进程的锁的id,那么其id还是不同的,说明其是不同的对象,但是因为这个锁的对象是在主进程创建的,子进程只是在自己的内存中复制了主进程中锁的状态,所以尽管不同子进程内存中也有自己的锁对象,但是这个锁对象的状态是一样的,这是最本质的,即我们实际上是需要不同线程或进程的锁是同步的,状态是一致,就可以认为是同一把锁,从而可以实现保护作用。具体的实现方式见如下代码。这样可以实现一个进程的两个print都执行完之后,另一个进程才会print,这一般在I/O里面多进程文件写入时会遇到。

from multiprocessing import Process,Lock
import time

def f(x,lock):
    lock.acquire()
    print(x+'1',id(lock))
    time.sleep(5)
    print(x+'2',id(lock))
    lock.release()

if __name__=='__main__':
    lock=Lock()
    p1=Process(target=f,args=('x',lock))
    p2=Process(target=f,args=('y',lock))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

       最后需要注意的一点是,创建多线程或多进程,都必须要先经过主模块判断,即必须在if __name__=='__main__':语句之后才行,这是为了保护资源的一种强制性机制。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值