Python中的锁都具都有哪些?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python中的锁都具都有哪些?相关的知识,希望对你有一定的参考价值。

大致罗列一下:
一、全局解释器锁(GIL)
1、什么是全局解释器锁
每个CPU在同一时间只能执行一个线程,那么其他的线程就必须等待该线程的全局解释器,使用权消失后才能使用全局解释器,即使多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。GIL的设计简化了CPython的实现,使的对象模型包括关键的内建类型,如:字典等,都是隐含的,可以并发访问的,锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
2、全局解释器锁的好处
1)、避免了大量的加锁解锁的好处
2)、使数据更加安全,解决多线程间的数据完整性和状态同步
3、全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
4、GIL的作用:
多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
二、同步锁
1、什么是同步锁?
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
2、为什么用同步锁?
因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。
3、怎么使用同步锁?
只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。
4、同步锁的所用:
为了保证解释器级别下的自己编写的程序唯一使用共享资源产生了同步锁。
三、死锁
1、什么是死锁?
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源或者程序推进顺序不当而相互等待的一个现象。
2、死锁产生的必要条件?
互斥条件、请求和保持条件、不剥夺条件、环路等待条件
3、处理死锁的基本方法?
预防死锁、避免死锁(银行家算法)、检测死锁(资源分配)、解除死锁:剥夺资源、撤销进程
四、递归锁
在Python中为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。递归锁分为可递归锁与非递归锁。
五、乐观锁
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
六、悲观锁
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
python常用的加锁方式:互斥锁、可重入锁、迭代死锁、互相调用死锁、自旋锁大致罗列一下:
一、全局解释器锁(GIL)
1、什么是全局解释器锁
每个CPU在同一时间只能执行一个线程,那么其他的线程就必须等待该线程的全局解释器,使用权消失后才能使用全局解释器,即使多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。GIL的设计简化了CPython的实现,使的对象模型包括关键的内建类型,如:字典等,都是隐含的,可以并发访问的,锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
2、全局解释器锁的好处
1)、避免了大量的加锁解锁的好处
2)、使数据更加安全,解决多线程间的数据完整性和状态同步
3、全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
4、GIL的作用:
多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
二、同步锁
1、什么是同步锁?
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
2、为什么用同步锁?
因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。
3、怎么使用同步锁?
只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。
4、同步锁的所用:
为了保证解释器级别下的自己编写的程序唯一使用共享资源产生了同步锁。
三、死锁
1、什么是死锁?
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源或者程序推进顺序不当而相互等待的一个现象。
2、死锁产生的必要条件?
互斥条件、请求和保持条件、不剥夺条件、环路等待条件
3、处理死锁的基本方法?
预防死锁、避免死锁(银行家算法)、检测死锁(资源分配)、解除死锁:剥夺资源、撤销进程
四、递归锁
在Python中为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。递归锁分为可递归锁与非递归锁。
五、乐观锁
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
六、悲观锁
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
python常用的加锁方式:互斥锁、可重入锁、迭代死锁、互相调用死锁、自旋锁
参考技术A 目录
GIL解决了Python中的什么问题?
为什么选取GIL作为解决方案?
对多线程Python程序的影响
为什么GIL还没有被删除?
为什么在Python 3 中GIL没有被移除?
如何处理Python中的GIL?
我们所说的Python全局解释锁(GIL)简单来说就是一个互斥体(或者说锁),这样的机制只允许一个线程来控制Python解释器。
这就意味着在任何一个时间点只有一个线程处于执行状态。GIL对执行单线程任务的程序员们来说并没什么显著影响,但是它成为了计算密集型(CPU-bound)和多线程任务的性能瓶颈。
由于GIL即使在拥有多个CPU核的多线程框架下都只允许一次运行一个线程,所以在Python众多功能中其声誉可谓是“臭名昭著”。
在这篇文章中,你将了解到GIL是如何影响到你的Python程序性能的以及如何减轻它对代码带来的影响。
GIL解决了Python中的什么问题?
Python利用引用计数来进行内存管理,这就意味着在Python中创建的对象都有一个引用计数变量来追踪指向该对象的引用数量。当数量为0时,该对象占用的内存即被释放。
我们来通过一个简单的代码演示引用计数是如何工作的:

在上述例子中,空列表对象[ ]的引用计数为3。该列表对象被a、b和传递给sys.getrefcount( )的参数引用。
回到GIL本身:
问题在于,这个引用计数变量需要在两个线程同时增加或减少时从竞争条件中得到保护。如果发生了这种情况,可能会导致泄露的内存永远不会被释放,抑或更严重的是当一个对象的引用仍然存在的情况下错误地释放内存。这可能会导致Python程序崩溃或带来各种诡异的bug。
通过对跨线程分享的数据结构添加锁定以至于数据不会不一致地被修改,这样做可以很好的保证引用计数变量的安全。
但是对每一个对象或者对象组添加锁意味着会存在多个锁这也就导致了另外一个问题——死锁(只有当存在多个锁时才会发生)。而另一个副作用是由于重复获取和释放锁而导致的性能下降。
GIL是解释器本身的一个单一锁,它增加的一条规则表明任何Python字节码的执行都需要获取解释锁。这有效地防止了死锁(因为只存在一个锁)并且不会带来太多的性能开销。但是这的确使每一个计算密集型任务变成了单线程。
GIL虽然也被其他语言解释器使用(如Ruby),但是这不是解决这个问题的唯一办法。一些编程语言通过使用除引用计数以外的方法(如垃圾收集)来避免GIL对线程安全内存管理的请求。
从另一方面来看,这也意味着这些语言通常需要添加其他性能提升功能(如JIT编译器)来弥补GIL单线程性能优势的损失。
为什么选取GIL作为解决方案?
那么为什么在Python中使用了这样一种看似绊脚石的技术呢?这是Python开发人员的一个错误决定么?
正如Larry Hasting所说,GIL的设计决定是Python如今受到火热追捧的重要原因之一。
当操作系统还没有线程的概念的时候Python就一直存在着。Python设计的初衷是易于使用以便更快捷地开发,这也使得越来越多的程序员开始使用Python。
人们针对于C库中那些被Python所需的功能写了许多扩展,为了防止不一致变化,这些C扩展需要线程安全内存管理,而这些正是GIL所提供的。
GIL是非常容易实现而且很容易添加到Python中。因为只需要管理一个锁所以对于单线程任务来说带来了性能提升。
非线程安全的C库变得更容易集成,而这些C扩展则成为Python被不同社区所接受的原因之一。
正如您所看到的,GIL是CPython开发者在早期Python生涯中面对困难问题的一种实用解决方案。
对多线程Python程序的影响
当你留意一些典型的Python程序或任何计算机程序时你会发现一个程序针对计算密集型和I/O密集型任务之间的性能表现是有所差异的。
计算密集型任务是那些促使CPU达到极限的任务。这其中包括了进行数学计算的程序,如矩阵相乘、搜索、图像处理等。
I/O密集型任务是一些需要花费时间来等待来自用户、文件、数据库、网络等的输入输出的任务。I/O密集型任务有时需要等待非常久直到他们从数据源获取到他们所需要的内容为止。这是因为在准备好输入输出之前数据源本身需要先进行自身处理。举例来说,一个用户考虑在输入提示中输入什么或者在其自己进程中运行的数据库查询。
让我们先来看一个执行倒计时的简单的计算密集型程序:

在我的4核系统上运行得到以下输出:

接下来我对代码做出微调,使用两个线程并行处理来完成倒计时:

接下来我再次运行:

正如你所看到的,两个版本的完成时间相差无几。在多线程版本中GIL阻止了计算密集型任务线程并行执行。
GIL对I/O密集型任务多线程程序的性能没有太大的影响,因为在等待I/O时锁可以在多线程之间共享。
但是对于一个线程是完全计算密集型的任务来说(例如,利用线程进行部分图像处理)不仅会由于锁而变成单线程任务而且还会明显的增加执行时间。正如上例中多线程与完全单线程相比的结果。
这种执行时间的增加是由于锁带来的获取和释放开销。
为什么GIL还没有被删除?
Python的开发者收到了许许多多关于这方面的抱怨,但是像Python这样极受欢迎的语言无法做出去除GIL这样的巨变同时还不造成向后不兼容问题。
GIL显然是可以被删除的,而且在过去这项任务也被开发者和研究人员多次完成。但是所有的尝试打破了在很大程度上取决于由GIL提供解决方案的C扩展市场。
当然,还有许多其他解决方案可以解决GIL问题,但是其中一些以牺牲单线程和多线程I/O密集型任务的性能表现为代价,而另外一些解决方法又过于复杂。毕竟新版本发布后你不会希望你的Python跑得慢了些。
BDFL of Python的创始人Guido van Rossum在2007年09月的文章《It isn’t Easy to remove the GIL》中向社区做出回答:
“如果单线程任务和多线程I/O密集型任务的性能表现不会下降,那么我十分希望Py3k中能出现一组修补程序。”
当然了,此后的每一次尝试都没有满足这个条件。
为什么在Python 3 中GIL没有被移除?
Python3中的确有机会使得许多功能从零开始,并且在这个过程中打破了那些需要更改和更新的C扩展并且将其移植到Python 3中。这也是为什么Python 3的早期版本被社区采纳的较慢的原因。
但是为什么GIL没有被删除?
删除GIL会使得Python 3在处理单线程任务方面比Python 2慢,可以想像会产生什么结果。你不能否认GIL带来的单线程性能优势,这也就是为什么Python 3中仍然还有GIL。
但是Python 3的确对现有GIL做了重大改进。
我们仅仅讨论了GIL对“仅计算密集型任务”和“仅I/O密集型任务”的影响,但是对于那些一部分线程是计算密集型一部分线程是I/O密集型的程序来说会怎么样呢?
在这样的程序中,Python的GIL通过不让I/O密集型线程从计算密集型线程获取GIL而使I/O密集型线程陷入瘫痪。
这是因为Python中内嵌了一种机制,这个机制在固定连续使用时间后强迫线程释放GIL,并且如果没人获取这个GIL,那么同一线程可以继续使用。

这个机制面临的问题是大多数计算密集型线程会在别的线程获取GIL之前再次获取GIL。这个研究工作由David Beazley进行,并且你可以在这里得到可视化资源。
Antoine Pitrou于2009年在Python3.2中解决了这个问题,他添加了一种机制来查看其他线程请求GIL的访问数量,当数量下降时不允许当前线程在其他线程有机会运行之前重新获取GIL。
如何处理Python中的GIL?
如果GIL给你带来困扰,你可尝试一下方法:
多进程vs多线程:最流行的方法是应用多进程方法,在这个方法中你使用多个进程而不是多个线程。每一个Python进程都有自己的Python解释器和内存空间,因此GIL不会成为问题。Python拥有一个multiprocessing模块可以帮助我们轻松创建多进程:

在系统上运行得到

相比于多线程版本,性能有所提升。
但是时间并没有下降到我们之前版本的一半,这是因为进程管理有自己的开销。多进程比多线程更“重”,因此请记住,这可能成为规模瓶颈。
替代Python解释器:Python中有多个解释器实现办法,分别用C,Java,C#和Python编写的CPython,JPython,IronPython和PyPy是最受欢迎的。GIL只存在于传统的Python实现方法如CPython中。如果你的程序及其库文件可以通过别的实现方式实现,那么你也可以尝试一下。
等等看吧:许多用户利用GIL提升了单线程任务性能表现。当然多线程程序员们也不必为此烦恼,因为Python社区内的一些聪明大脑们正在致力于从CPython中删除GIL。其中一种尝试为Giletomy。
Python GIL经常被认为是一个神秘而困难的话题。但是请记住作为一名Python支持者,只有当您正在编写C扩展或者您的程序中有计算密集型的多线程任务时才会被GIL影响。
在这种情况下,这篇文章应该给了你需要的一切去了解GIL是什么以及如何在自己的项目中处理它。如果您希望了解GIL的低层次内部运行,我建议您观看David Beazley的Understanding the Python GIL。
在互联网发展的不同阶段,达内的课程紧跟互联网发展的步伐,15年不断的创新,打造覆盖IT全产业链的职业课程版图。

在 Unix 上的 C 中,进程如何在不打开文件的情况下知道它对文件具都有哪些权限?

【中文标题】在 Unix 上的 C 中,进程如何在不打开文件的情况下知道它对文件具都有哪些权限?【英文标题】:In C on Unix, how can a process tell what permissions it has to a file without opening it?在 Unix 上的 C 中,进程如何在不打开文件的情况下知道它对文件具有哪些权限? 【发布时间】:2010-11-11 10:19:59 【问题描述】:

我可以使用 stat() 来确定所有者、组或其他人拥有哪些权限,并且我可以使用 geteuid() 和 getpwuid() 来获取进程的用户名。我不太确定如何在没有系统调用的情况下获取用户所属的组。

即使知道如何获取这些组,整合所有这些信息似乎也需要做很多工作。有没有更简单的方法?

【问题讨论】:

【参考方案1】:

access() 检查路径参数指向的文件名。这里的缺点是每个文件权限都必须使用下面的标志单独检查。 R_OK 测试读取权限。 W_OK 测试写权限。 X_OK 测试执行或搜索权限。 F_OK 检查文件是否存在

【讨论】:

【参考方案2】:

unistd.h 定义了一个 access() 函数,

int access(const char *path, int amode);

其中 path 是您的文件名,amode 是要检查的访问权限的按位包含 OR。

R_OK、W_OK 和 X_OK 分别保存用于检查读取、写入和搜索/执行权限的模式值。

int readable, readwritable;

//checking for read access
readable = access("/usr/bin/file", R_OK);

//checking for read and write access
readwritable = access("/usr/bin/file", R_OK|W_OK);

您可以在 unix 手册页中找到 access() 的完整描述。

【讨论】:

【参考方案3】:

access() POSIX function 无需打开即可查看权限。但是,它需要一个系统调用。

access() 函数应根据 amode 中包含的位模式检查由 path 参数指向的路径名命名的文件的可访问性,使用真实用户 ID 代替有效用户 ID 和真实组 ID代替有效的组 ID。

例如:

access("/etc/passwd",W_OK)

检查您是否具有对 passwd 文件的写入权限。使用 R_OK,检查读取权限。

eaccess() function(euidaccess 是同义词)使用有效的用户和组 ID。虽然 eaccess 似乎得到了广泛的支持,但据我所知,它不是 POSIX 标准的一部分。

【讨论】:

eaccess 是非标准的,但是 POSIX.1-2008 添加了 faccessat,它可以与标志 AT_EACCESS 一起使用,使用有效 id 执行访问检查。然而,这还没有得到广泛的支持。 opengroup.org/onlinepubs/9699919799/functions/faccessat.html

以上是关于Python中的锁都具都有哪些?的主要内容,如果未能解决你的问题,请参考以下文章

分布式锁都有哪些实现方案?

分布式分布式锁都有哪些实现方案?

当未使用 Environment.Exit() 时,.net 程序可以具都有哪些退出代码?

在 Unix 上的 C 中,进程如何在不打开文件的情况下知道它对文件具都有哪些权限?

Linux操作系统具都有哪些特点

面向对象程序设计具都有哪些特性呢?