简单探讨一下python线程锁

2023-05-17 08:05:42 线程 简单 探讨

为什么需要线程锁

当我们访问一些特殊的数据时,需要保证该数据的原子性,比如: 文章的阅读量、文章的点赞量等。我们必须要确保这些共享数据必须是原子性的,否则的话,多线程同时访问的时候,可能会出现异常情况,什么是原子性呢?原子性是指一个操作是不可能被中途中断的,要不全部完成,要不没有完成,这么说,可能不理解,下面举个例子来说明一下原子性的重要性:

比如我们写一个程序,来模拟10个人点赞,最后输出阅读量的值,代码如下:

代码初看好像没什么问题,反正核心点就是阅读量+1,当运行就,就出现问题了,

上述代码,运行了5次,出现了5次不一样的结果。这就是出现了竞争,所以我们需要加一个锁,来确保该阅读量函数的原子性。

线程锁使用

python中的threading模块,提供的锁就只有一种,我们使用Lock定义锁,例如:

Lock = threading.Lock()

如果需要对资源进行上锁的话,使用acquire方法即可,例如:

Lock.acquire()

如果需要对资源进行解锁的话,使用release方法即可,例如:

Lock.release()

除此之外,threading模块还提供了RLock可重入锁,可重入锁的作用和普通锁一样,用于保护资源,以保证并发访问不会出现问题,但是与普通锁不同的是,可重入锁允许同一个线程在持有锁的情况下多次获取该锁而不会发生死锁。但是在持有多少次锁,也必须释放多少次锁,否则会出现问题。

案例说明线程锁

上述我们介绍了线程锁,这里将addReaderCount进行加锁和解锁的操作,代码如下:

上述代码在源代码的基础上,在最开始使用Lock定义了一个锁,而后再函数开始之前进行一个加锁acquire(),这样的话,其他函数在执行的时候,发现这个是已经锁住了的,所以会等待,等待解锁后(releas())再进行该函数,而函数又会加锁,如此往复,能够确保在同一时刻,addReaderCount函数只执行一次。

执行程序结果为:

可以发现,这次的结果阅读量的值终于符合我们的预期了。

实现一个读写锁

Python中,原生是没有读写锁的,只能借助第三方来实现,这里我们可以实现一个简单的读写锁。

在写之前,我们需要先了解下读写锁的作用到底是什么?读写锁将资源拆分为读和写2种操作,读锁允许多个线程同时读取共享资源,提高并发性能。但是当需要修改共享资源的时候,只能允许一个线程修改该共享资源,并且在修改完毕前,不允许读请求,以便造成数据不一致。所以操作完毕后,开放读请求。

这里为了实验,写了一个最简单的读写锁,安全性和效率都得不到保证,若想使用读写锁,可以使用第三方的读写锁。

我们自己写的读写锁代码如下:

上述代码,我们实现了一个读写锁类RWLock,其中有4个方法,分别为:

  • writeLock:写锁
  • writeUnlock:释放写锁
  • readLock:读锁
  • readUnlock: 释放读锁

由于读锁可以同一时刻被调用,所以我们使用的变量readCount来定义的,等需要读锁的是,readCount会+1,可以一直调用下去,而如果需要写锁的时候,这里是先将写锁变量Locked定义为True,若此时再次调用读写,会让其等待,而在写锁中,会等待读锁读取完毕后(readCount为0的时候),再进行写锁的请求。写锁执行完毕后,会将Locked给重置为False

这里写一个小的调用方法来调用一下:

上述代码,在readswrites方法中,分表调用我们之前定义的读锁和写锁,代码中定义了9个线程,分别是调用了6次读,以及3次写。

效果我们来看下:

从上述结果中,我们不难发现,在没有写锁情况下,我们使用读锁,可以访问同一资源,当使用写锁后,读锁会暂停,当写锁执行完毕后,读锁才会继续批量执行。

总结

本篇文章简单探讨了一下python线程锁,并且举了一个点赞的案例,来说明某些资源/函数需要原子性操作的时候,为避免资源竞争,所以需要在使用过程中,增加一个锁,以保证其原子性,由于python原生没有读写锁,需要依赖于第三方,所以最后我们写了一个最简单的读写锁。

到此这篇关于简单探讨一下python线程锁的文章就介绍到这了,更多相关python 线程锁内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章