- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章深入解析Python中的线程同步方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
同步访问共享资源 。
在使用线程的时候,一个很重要的问题是要避免多个线程对同一变量或其它资源的访问冲突。一旦你稍不留神,重叠访问、在多个线程中修改(共享资源)等这些操作会导致各种各样的问题;更严重的是,这些问题一般只会在比较极端(比如高并发、生产服务器、甚至在性能更好的硬件设备上)的情况下才会出现。 比如有这样一个情况:需要追踪对一事件处理的次数 。
1
2
3
4
5
6
|
counter
=
0
def
process_item(item):
global
counter
... do something with item ...
counter
+
=
1
|
如果你在多个线程中同时调用这个函数,你会发现counter的值不是那么准确。在大多数情况下它是对的,但有时它会比实际的少几个。 出现这种情况的原因是,计数增加操作实际上分三步执行
考虑一下这种情况:在当前线程获取到counter值后,另一个线程抢占到了CPU,然后同样也获取到了counter值,并进一步将counter值重新计算并完成回写;之后时间片重新轮到当前线程(这里仅作标识区分,并非实际当前),此时当前线程获取到counter值还是原来的,完成后续两步操作后counter的值实际只加上1。 另一种常见情况是访问不完整或不一致状态。这类情况主要发生在一个线程正在初始化或更新数据时,另一个进程却尝试读取正在更改的数据.
原子操作 实现对共享变量或其它资源的同步访问最简单的方法是依靠解释器的原子操作。原子操作是在一步完成执行的操作,在这一步中其它线程无法获得该共享资源。 通常情况下,这种同步方法只对那些只由单个核心数据类型组成的共享资源有效,譬如,字符串变量、数字、列表或者字典等。下面是几个线程安全的操作:
注意,上面提到过,对一个变量或者属性进行读操作,然后修改它,最终将其回写不是线程安全的。因为另外一个线程会在这个线程读完却没有修改或回写完成之前更改这个共享变量/属性.
锁 。
锁是Python的threading模块提供的最基本的同步机制。在任一时刻,一个锁对象可能被一个线程获取,或者不被任何线程获取。如果一个线程尝试去获取一个已经被另一个线程获取到的锁对象,那么这个想要获取锁对象的线程只能暂时终止执行直到锁对象被另一个线程释放掉。 锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
1
2
3
4
5
|
lock
=
Lock()
lock.acquire()
#: will block if lock is already held
... access shared resource
lock.release()
|
注意,即使在访问共享资源的过程中出错了也应该释放锁,可以用try-finally来达到这一目的:
1
2
3
4
5
|
lock.acquire()
try
:
... access shared resource
finally
:
lock.release()
#: release lock, no matter what
|
在Python 2.5及以后的版本中,你可以使用with语句。在使用锁的时候,with语句会在进入语句块之前自动的获取到该锁对象,然后在语句块执行完成后自动释放掉锁:
1
2
3
4
|
from
__future__
import
with_statement
#: 2.5 only
with lock:
... access shared resource
|
acquire方法带一个可选的等待标识,它可用于设定当有其它线程占有锁时是否阻塞。如果你将其值设为False,那么acquire方法将不再阻塞,只是如果该锁被占有时它会返回False
1
2
3
4
5
6
7
|
if
not
lock.acquire(
False
):
... 锁资源失败
else
:
try
:
... access shared resource
finally
:
lock.release()
|
你可以使用locked方法来检查一个锁对象是否已被获取,注意不能用该方法来判断调用acquire方法时是否会阻塞,因为在locked方法调用完成到下一条语句(比如acquire)执行之间该锁有可能被其它线程占有.
1
2
3
|
if
not
lock.locked():
#: 其它线程可能在下一条语句执行之前占有了该锁
lock.acquire()
#: 可能会阻塞
|
简单锁的缺点 标准的锁对象并不关心当前是哪个线程占有了该锁;如果该锁已经被占有了,那么任何其它尝试获取该锁的线程都会被阻塞,即使是占有锁的这个线程。考虑一下下面这个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
lock
=
threading.Lock()
def
get_first_part():
lock.acquire()
try
:
... 从共享对象中获取第一部分数据
finally
:
lock.release()
return
data
def
get_second_part():
lock.acquire()
try
:
... 从共享对象中获取第二部分数据
finally
:
lock.release()
return
data
|
示例中,我们有一个共享资源,有两个分别取这个共享资源第一部分和第二部分的函数。两个访问函数都使用了锁来确保在获取数据时没有其它线程修改对应的共享数据。 现在,如果我们想添加第三个函数来获取两个部分的数据,我们将会陷入泥潭。一个简单的方法是依次调用这两个函数,然后返回结合的结果:
1
2
3
4
|
def
get_both_parts():
first
=
get_first_part()
seconde
=
get_second_part()
return
first, second
|
这里的问题是,如有某个线程在两个函数调用之间修改了共享资源,那么我们最终会得到不一致的数据。最明显的解决方法是在这个函数中也使用lock
1
2
3
4
5
6
7
8
|
def
get_both_parts():
lock.acquire()
try
:
first
=
get_first_part()
seconde
=
get_second_part()
finally
:
lock.release()
return
first, second
|
然而,这是不可行的。里面的两个访问函数将会阻塞,因为外层语句已经占有了该锁。为了解决这个问题,你可以通过使用标记在访问函数中让外层语句释放锁,但这样容易失去控制并导致出错。幸运的是,threading模块包含了一个更加实用的锁实现:re-entrant锁。 Re-Entrant Locks (RLock) 。
RLock类是简单锁的另一个版本,它的特点在于,同一个锁对象只有在被其它的线程占有时尝试获取才会发生阻塞;而简单锁在同一个线程中同时只能被占有一次。如果当前线程已经占有了某个RLock锁对象,那么当前线程仍能再次获取到该RLock锁对象.
1
2
3
4
5
6
7
|
lock
=
threading.Lock()
lock.acquire()
lock.acquire()
#: 这里将会阻塞
lock
=
threading.RLock()
lock.acquire()
lock.acquire()
#: 这里不会发生阻塞
|
RLock的主要作用是解决嵌套访问共享资源的问题,就像前面描述的示例。要想解决前面示例中的问题,我们只需要将Lock换为RLock对象,这样嵌套调用也会OK. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
lock
=
threading.RLock()
def
get_first_part():
... see above
def
get_second_part():
... see above
def
get_both_parts():
... see above
|
这样既可以单独访问两部分数据也可以一次访问两部分数据而不会被锁阻塞或者获得不一致的数据。 注意RLock会追踪递归层级,因此记得在acquire后进行release操作。 Semaphores 。
信号量是一个更高级的锁机制。信号量内部有一个计数器而不像锁对象内部有锁标识,而且只有当占用信号量的线程数超过信号量时线程才阻塞。这允许了多个线程可以同时访问相同的代码区.
1
2
|
semaphore
=
threading.BoundedSemaphore()
semaphore.acquire()
#: counter减小
|
... 访问共享资源 。
1
|
semaphore.release()
#: counter增大
|
当信号量被获取的时候,计数器减小;当信号量被释放的时候,计数器增大。当获取信号量的时候,如果计数器值为0,则该进程将阻塞。当某一信号量被释放,counter值增加为1时,被阻塞的线程(如果有的话)中会有一个得以继续运行。 信号量通常被用来限制对容量有限的资源的访问,比如一个网络连接或者数据库服务器。在这类场景中,只需要将计数器初始化为最大值,信号量的实现将为你完成剩下的事情.
1
2
3
|
max_connections
=
10
semaphore
=
threading.BoundedSemaphore(max_connections)
|
。
如果你不传任何初始化参数,计数器的值会被初始化为1. Python的threading模块提供了两种信号量实现。Semaphore类提供了一个无限大小的信号量,你可以调用release任意次来增大计数器的值。为了避免错误出现,最好使用BoundedSemaphore类,这样当你调用release的次数大于acquire次数时程序会出错提醒。 线程同步 。
锁可以用在线程间的同步上。threading模块包含了一些用于线程间同步的类。 Events 。
一个事件是一个简单的同步对象,事件表示为一个内部标识(internal flag),线程等待这个标识被其它线程设定,或者自己设定、清除这个标识.
1
2
3
4
5
6
7
8
|
event
=
threading.Event()
#: 一个客户端线程等待flag被设定
event.wait()
#: 服务端线程设置或者清除flag
event.
set
()
event.clear()
|
一旦标识被设定,wait方法就不做任何处理(不会阻塞),当标识被清除时,wait将被阻塞直至其被重新设定。任意数量的线程可能会等待同一个事件。 Conditions 。
条件是事件对象的高级版本。条件表现为程序中的某种状态改变,线程可以等待给定条件或者条件发生的信号。 下面是一个简单的生产者/消费者实例。首先你需要创建一个条件对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#: 表示一个资源的附属项
condition
=
threading.Condition()
生产者线程在通知消费者线程有新生成资源之前需要获得条件:
#: 生产者线程
... 生产资源项
condition.acquire()
... 将资源项添加到资源中
condition.notify()
#: 发出有可用资源的信号
condition.release()
消费者必须获取条件(以及相关联的锁),然后尝试从资源中获取资源项:
#: 消费者线程
condition.acquire()
while
True
:
...从资源中获取资源项
if
item:
break
condition.wait()
#: 休眠,直至有新的资源
condition.release()
... 处理资源
|
wait方法释放了锁,然后将当前线程阻塞,直到有其它线程调用了同一条件对象的notify或者notifyAll方法,然后又重新拿到锁。如果同时有多个线程在等待,那么notify方法只会唤醒其中的一个线程,而notifyAll则会唤醒全部线程。 为了避免在wait方法处阻塞,你可以传入一个超时参数,一个以秒为单位的浮点数。如果设置了超时参数,wait将会在指定时间返回,即使notify没被调用。一旦使用了超时,你必须检查资源来确定发生了什么。 注意,条件对象关联着一个锁,你必须在访问条件之前获取这个锁;同样的,你必须在完成对条件的访问时释放这个锁。在生产代码中,你应该使用try-finally或者with. 可以通过将锁对象作为条件构造函数的参数来让条件关联一个已经存在的锁,这可以实现多个条件公用一个资源:
1
2
3
|
lock
=
threading.RLock()
condition_1
=
threading.Condition(lock)
condition_2
=
threading.Condition(lock)
|
互斥锁同步 我们先来看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import
time, threading
# 假定这是你的银行存款:
balance
=
0
muxlock
=
threading.Lock()
def
change_it(n):
# 先存后取,结果应该为0:
global
balance
balance
=
balance
+
n
balance
=
balance
-
n
def
run_thread(n):
# 循环次数一旦多起来,最后的数字就变成非0
for
i
in
range
(
100000
):
change_it(n)
t1
=
threading.Thread(target
=
run_thread, args
=
(
5
,))
t2
=
threading.Thread(target
=
run_thread, args
=
(
8
,))
t3
=
threading.Thread(target
=
run_thread, args
=
(
9
,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print
balance
|
结果
1
2
3
4
5
6
7
8
|
[
/data/web/test_python
]$ python multhread_threading.py
0
[
/data/web/test_python
]$ python multhread_threading.py
61
[
/data/web/test_python
]$ python multhread_threading.py
0
[
/data/web/test_python
]$ python multhread_threading.py
24
|
上面的例子引出了多线程编程的最常见问题:数据共享。当多个线程都修改某一个共享数据的时候,需要进行同步控制。 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性.
threading模块中定义了Lock类,可以方便的处理锁定:
1
2
3
|
#创建锁mutex = threading.Lock()
#锁定mutex.acquire([timeout])
#释放mutex.release()
|
其中,锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。 使用互斥锁实现上面的例子的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
balance
=
0
muxlock
=
threading.Lock()
def
change_it(n):
# 获取锁,确保只有一个线程操作这个数
muxlock.acquire()
global
balance
balance
=
balance
+
n
balance
=
balance
-
n
# 释放锁,给其他被阻塞的线程继续操作
muxlock.release()
def
run_thread(n):
for
i
in
range
(
10000
):
change_it(n)
|
加锁后的结果,就能确保数据正确:
1
2
3
4
5
6
7
8
|
[
/data/web/test_python
]$ python multhread_threading.py
0
[
/data/web/test_python
]$ python multhread_threading.py
0
[
/data/web/test_python
]$ python multhread_threading.py
0
[
/data/web/test_python
]$ python multhread_threading.py
0
|
。
最后此篇关于深入解析Python中的线程同步方法的文章就讲到这里了,如果你想了解更多关于深入解析Python中的线程同步方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我将 Bootstrap 与 css 和 java 脚本结合使用。在不影响前端代码的情况下,我真的很难在css中绘制这个背景。在许多问题中,人们将宽度和高度设置为 0%。但是由于我的导航栏,我不能使用
我正在用 c 编写一个程序来读取文件的内容。代码如下: #include void main() { char line[90]; while(scanf("%79[^\
我想使用 javascript 获取矩阵数组的所有对 Angular 线。假设输入输出如下: input = [ [1,2,3], [4,5,6], [7,8,9], ] output =
可以用pdfmake绘制lines,circles和other shapes吗?如果是,是否有documentation或样本?我想用jsPDF替换pdfmake。 最佳答案 是的,有可能。 pdfm
我有一个小svg小部件,其目的是显示角度列表(参见图片)。 现在,角度是线元素,仅具有笔触,没有填充。但是现在我想使用一种“内部填充”颜色和一种“笔触/边框”颜色。我猜想line元素不能解决这个问题,
我正在为带有三角对象的 3D 场景编写一个非常基本的光线转换器,一切都工作正常,直到我决定尝试从场景原点 (0/0/0) 以外的点转换光线。 但是,当我将光线原点更改为 (0/1/0) 时,相交测试突
这个问题已经有答案了: Why do people write "#!/usr/bin/env python" on the first line of a Python script? (22 个回
如何使用大约 50 个星号 * 并使用 for 循环绘制一条水平线?当我尝试这样做时,结果是垂直(而不是水平)列出 50 个星号。 public void drawAstline() { f
这是一个让球以对角线方式下降的 UI,但球保持静止;线程似乎无法正常工作。你能告诉我如何让球移动吗? 请下载一个球并更改目录,以便程序可以找到您的球的分配位置。没有必要下载足球场,但如果您愿意,也可以
我在我的一个项目中使用 Jmeter 和 Ant,当我们生成报告时,它会在报告中显示 URL、#Samples、失败、成功率、平均时间、最短时间、最长时间。 我也想在报告中包含 90% 的时间线。 现
我有一个不寻常的问题,希望有人能帮助我。我想用 Canvas (android) 画一条 Swing 或波浪线,但我不知道该怎么做。它将成为蝌蚪的尾部,所以理想情况下我希望它的形状更像三角形,一端更大
这个问题已经有答案了: Checking Collision of Shapes with JavaFX (1 个回答) 已关闭 8 年前。 我正在使用 JavaFx 8 库。 我的任务很简单:我想检
如何按编号的百分比拆分文件。行数? 假设我想将我的文件分成 3 个部分(60%/20%/20% 部分),我可以手动执行此操作,-_-: $ wc -l brown.txt 57339 brown.tx
我正在努力实现这样的目标: 但这就是我设法做到的。 你能帮我实现预期的结果吗? 更新: 如果我删除 bootstrap.css 依赖项,问题就会消失。我怎样才能让它与 Bootstrap 一起工作?
我目前正在构建一个网站,但遇到了 transform: scale 的问题。我有一个按钮,当用户将鼠标悬停在它上面时,会发生两件事: 背景以对 Angular 线“扫过” 按钮标签颜色改变 按钮稍微变
我需要使用直线和仿射变换绘制大量数据点的图形(缩放图形以适合 View )。 目前,我正在使用 NSBezierPath,但我认为它效率很低(因为点在绘制之前被复制到贝塞尔路径)。通过将我的数据切割成
我正在使用基于 SVM 分类的 HOG 特征检测器。我可以成功提取车牌,但提取的车牌除了车牌号外还有一些不必要的像素/线。我的图像处理流程如下: 在灰度图像上应用 HOG 检测器 裁剪检测到的区域 调
我有以下图片: 我想填充它的轮廓(即我想在这张图片中填充线条)。 我尝试了形态学闭合,但使用大小为 3x3 的矩形内核和 10 迭代并没有填满整个边界。我还尝试了一个 21x21 内核和 1 迭代,但
我必须找到一种算法,可以找到两组数组之间的交集总数,而其中一个数组已排序。 举个例子,我们有这两个数组,我们向相应的数字画直线。 这两个数组为我们提供了总共 7 个交集。 有什么样的算法可以帮助我解决
简单地说 - 我想使用透视投影从近裁剪平面绘制一条射线/线到远裁剪平面。我有我认为是使用各种 OpenGL/图形编程指南中描述的方法通过单击鼠标生成的正确标准化的世界坐标。 我遇到的问题是我的光线似乎
我是一名优秀的程序员,十分优秀!