The Little Book of Semaphores 信号量小书 第八章 Python中的同步

第八章 Python中的同步

通过使用伪代码,我们避免了现实世界中一些丑陋的同步细节。 在本章中,我们将介绍Python中的实际同步代码; 在下一章我们将看看C.

Python提供了一个相当令人愉快的多线程环境,并配有Semaphore对象。 它有一些缺点,但附录A中有一些清理代码可以使事情变得更好。

这是一个简单的例子:

第一行运行附录A中的清理代码; 我将从其他例子中删除这一行。

Shared定义了一个包含共享变量的对象类型。 全局变量也在线程之间共享,但我们不会在这些示例中使用任何变量。 在函数内部声明的线程也是局部的,因为它们是特定于线程的。

子线程的代码是一个无限循环,递增计数器,打印新值,然后睡眠0.5秒。

父线程创建shared变量和两个子线程,然后等待子线程退出(在这种情况下,它们不会退出)。

8.1 互斥锁检查问题

关注同步问题的勤奋学生会注意到子线程对计数器进行了不同步的更新,这是不安全的! 如果您运行此程序,您可能会看到一些错误,也可能看不到。 同步错误的可怕之处是它们是不可预测的,这意味着即使广泛的测试也可能无法发现它们。

为了检测错误,经常需要自动化搜索。 在这种情况下,我们可以通过跟踪计数器的值来检测错误。

在此示例中,shared包含一个列表list(误导性命名为数组array),用于跟踪计数器的每个值的使用次数。 每次循环时,子线程都会检查counter计数器,如果超过end则退出。 如果不是,则使用counter作为数组的索引并递增相应的条目。 然后他们递增counter。

如果一切正常,数组中的每个条目应该只增加一次。 当子线程退出时,父节点从join返回并打印数组的值。当运行这个程序时,我得到:

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

这是令人失望的正确。 如果我们增加数组的大小,我们可能会期望更多的错误,但是检查结果也变得更加困难。

我们可以通过对数组的结果生成直方图来自动检查:

现在,运行程序后,我得到:

{1:10}

这意味着值1出现了10次,如预期的那样。 到目前为止没有任何错误,但如果我们把end变得更大,事情变得更有趣:

end = 100, {1: 100}
end = 1000, {1: 1000}
end = 10000, {1: 10000}
end = 100000, {1: 27561, 2: 72439}

哎呀! 当end足够时,子线程之间有很多上下文切换,就开始出现同步错误。 在这种情况下,我们会遇到很多错误,这表明程序陷入了循环模式,其中线程在临界区中一直被中断。

这个例子演示了同步错误的一个危险,即它们可能是罕见的,但它们不是随机的。如果在一百万次中出现一次错误,那并不意味着它不会连续发生一百万次。

思考:向此程序添加同步代码以强制独占访问共享变量。 您可以从greenteapress.com/semaphores/counter.py下载本节中的代码。

8.1.1 互斥锁检查提示

以下是我使用Shared变量的版本:

唯一的变化就是名为mutex的信号量,这应该不足为奇。

8.1.2 互斥锁检查方案

以下是我的解决方案:

虽然这不是本书中最难的同步问题,但是您可能已经发现要正确处理细节非常棘手。特别是,很容易忘记在中断循环之前发出互斥锁信号,这会导致死锁。

我使用end = 1000000运行此解决方案,并得到以下结果:

{1:1000000}

当然,这并不意味着我的解决方案是正确的,但它是一个良好的开端。

8.2 可乐机问题

下面的程序模拟生产者和消费者从可乐机中添加和移除可乐:

机器的容量是10瓶,而机器最初是半满的。 所以共享变量cokes是5。

该程序创建4个线程,两个生产者和两个消费者。它们都运行循环loop,但生产者调用produce,消费者调用consume。这些函数对共享变量进行了不同步的访问,这是禁止的。

每次通过循环,生产者和消费者睡眠一段时间,该时间从mu的指数分布中选择。由于有两个生产者和两个消费者,平均每秒可以将两个可乐添加到机器中,并且有两个可乐被移除。

因此,焦炭的平均数是恒定的,但在短期内可以变化很大。如果运行该程序一段时间,您可能会看到cokes的数值跌到零以下,或爬升到10以上。当然,这两种情况都不应该发生。

思考:为此程序添加代码以强制执行以下同步约束:

  • 访问cokes应该是互斥的。
  • 如果cokes的数值是零,消费者应该阻塞,直到添加了可乐。
  • 如果cokes的数值是10,生产者应该阻塞,直到可乐被移除。

您可以从greenteapress.com/semaphores/coke.py下载该程序。

8.2.1 可乐机提示

以下是我在解决方案中使用的共享变量:

cokes现在是一个信号量(而不是一个简单的整数),这使得打印它的价值变得棘手。 当然,你永远不应该访问信号量的值,并且Python以其通常的do-gooder方式并不提供你在某些实现中看到的任何欺骗方法。

但您可能会发现,将信号量的值存储在一个名为_Semaphore__value的私有属性中是很有意思的。另外,在你不知道的情况下,Python实际上不会对访问私有属性实施任何限制。当然,你永远不应该访问它,但我认为你可能会感兴趣。

啊咳。

8.2.2 可乐机方案

如果您已阅读本书的其余部分,那么您应该毫不费力地提出至少与此类似的内容:

如果您运行这个程序一段时间,您应该能够确认机器中的可乐数量从不是负数或大于10。所以这个解决方案似乎是正确的。
到目前为止。

猜你喜欢

转载自blog.csdn.net/booksyhay/article/details/83009956