使用lock、Monitor Pulse和Wait同步线程
Using lock, Monitor Pulse and Wait to synchronize threads
我已经阅读了官方文档和大约 25 个教程,但我仍然在为如何同步而苦苦挣扎,比如说,3 个线程与 Monitor
Pulse()
和 Wait()
方法以及使用 lock
个对象。
(是的,我知道还有其他同步技术,但这应该是可行的,这让我很沮丧)
这是我提出的简单“概念验证”想法。
假设我有三个线程,每个线程都有一个任务:
- Thread1 运行一个打印出所有能被 3 整除的整数的任务
- Thread2 运行一个任务,打印出所有余数为
除以 3 时为 1
- Thread3 运行一个任务,打印出所有余数为
2 除以 3
我希望最终输出为:0,1,2,3,4,5,6,... 直到我可能选择的任何整数限制,但我们可以说 50 或 100 - 它不会没关系。
我想更全面地了解锁与 Monitor.Wait() 和 Monitor.Pulse() 的机制以及它们如何协同工作。
如果我理解正确,当线程遇到 lock(someObject) { ... }
时,如果它是那里的第一个线程,它就会获得对该临界区的独占访问权。在同一对象上遇到锁定的任何其他线程都卡在其各自代码中的该行(即 lock(someObject)
),对吗?
如果线程1有lock(someObject)
调用Monitor.Wait(someObject)
,那么线程1释放锁,进入等待 队列,对吗?然后,如果任何其他线程(例如,线程 2)调用 Monitor.Pulse(someObject)
,它会将线程 1 移入 ready 队列?
无论我尝试什么,代码似乎都只是 waiting/blocking 无限。
我想我的总结问题是:
- 使用
Pulse
和 Wait
同步三个线程是否需要多个锁对象?
Wait
和 Pulse
在这段代码中的位置?在用于迭代我们要打印的值的循环周围的锁内?在锁内,仅放置在条件内(例如,if (i % 3 == 2)
)?等等
非常感谢任何有用的输入!
更新(2021 年 8 月 7 日):
事实证明,考虑到我在单个文件中设置锁的方式,将锁设置为静态是必要的。我很生气之前没有注意到这一点,但是建议的在线文档(来自 Joe Albahari 的网站)非常有帮助。
下面是一个比较简单的Wait
/PulseAll
例子:
object locker = new();
int i = 0;
bool finished = false;
Thread[] threads = Enumerable.Range(0, 3).Select(remainder => new Thread(() =>
{
lock (locker)
{
try
{
do
{
while (!finished && i % 3 != remainder) Monitor.Wait(locker);
if (finished) break;
Console.WriteLine($"Worker #{remainder} produced {i}");
Monitor.PulseAll(locker);
} while (++i < 20);
}
finally { finished = true; Monitor.PulseAll(locker); }
}
})).ToArray();
Array.ForEach(threads, t => t.Start());
Array.ForEach(threads, t => t.Join());
创建了三个工作线程,由 remainder
参数标识,取值 0、1 和 2。每个工作线程负责生成其模 3 等于余数的数字。
int i
是循环变量,bool finished
是一个标志,当任何一个工人完成时,它会变成 true
。此标志确保在任何工作人员出错的情况下,其他工作人员不会死锁。
每个工人都进入一个临界区,该临界区包含一个do-while
循环,即数字生成和递增循环。在发出数字之前,它必须等待轮到它。 i % 3 == remainder
时轮到它了。否则它 Wait
s。轮到它时,它发出数字,它递增 i
,它 Pulse
是所有等待的工作人员,并继续下一次迭代。当循环结束时,它是 Pulse
释放锁前的最后一次。
选择了 PulseAll
而不是 Pulse
,因为我们不知道等待队列中的下一个工人是否是当前 i
的正确工人,所以我们把他们都叫醒。
输出:
Worker #0 produced 0
Worker #1 produced 1
Worker #2 produced 2
Worker #0 produced 3
Worker #1 produced 4
Worker #2 produced 5
Worker #0 produced 6
Worker #1 produced 7
Worker #2 produced 8
Worker #0 produced 9
Worker #1 produced 10
Worker #2 produced 11
Worker #0 produced 12
Worker #1 produced 13
Worker #2 produced 14
Worker #0 produced 15
Worker #1 produced 16
Worker #2 produced 17
Worker #0 produced 18
Worker #1 produced 19
注意:这个答案 1st revision 中的示例是有问题的,因为它创建了一个初始的忙等待阶段,直到所有工作人员都准备好了。
我已经阅读了官方文档和大约 25 个教程,但我仍然在为如何同步而苦苦挣扎,比如说,3 个线程与 Monitor
Pulse()
和 Wait()
方法以及使用 lock
个对象。
(是的,我知道还有其他同步技术,但这应该是可行的,这让我很沮丧)
这是我提出的简单“概念验证”想法。
假设我有三个线程,每个线程都有一个任务:
- Thread1 运行一个打印出所有能被 3 整除的整数的任务
- Thread2 运行一个任务,打印出所有余数为 除以 3 时为 1
- Thread3 运行一个任务,打印出所有余数为 2 除以 3
我希望最终输出为:0,1,2,3,4,5,6,... 直到我可能选择的任何整数限制,但我们可以说 50 或 100 - 它不会没关系。
我想更全面地了解锁与 Monitor.Wait() 和 Monitor.Pulse() 的机制以及它们如何协同工作。
如果我理解正确,当线程遇到 lock(someObject) { ... }
时,如果它是那里的第一个线程,它就会获得对该临界区的独占访问权。在同一对象上遇到锁定的任何其他线程都卡在其各自代码中的该行(即 lock(someObject)
),对吗?
如果线程1有lock(someObject)
调用Monitor.Wait(someObject)
,那么线程1释放锁,进入等待 队列,对吗?然后,如果任何其他线程(例如,线程 2)调用 Monitor.Pulse(someObject)
,它会将线程 1 移入 ready 队列?
无论我尝试什么,代码似乎都只是 waiting/blocking 无限。
我想我的总结问题是:
- 使用
Pulse
和Wait
同步三个线程是否需要多个锁对象? Wait
和Pulse
在这段代码中的位置?在用于迭代我们要打印的值的循环周围的锁内?在锁内,仅放置在条件内(例如,if (i % 3 == 2)
)?等等
非常感谢任何有用的输入!
更新(2021 年 8 月 7 日):
事实证明,考虑到我在单个文件中设置锁的方式,将锁设置为静态是必要的。我很生气之前没有注意到这一点,但是建议的在线文档(来自 Joe Albahari 的网站)非常有帮助。
下面是一个比较简单的Wait
/PulseAll
例子:
object locker = new();
int i = 0;
bool finished = false;
Thread[] threads = Enumerable.Range(0, 3).Select(remainder => new Thread(() =>
{
lock (locker)
{
try
{
do
{
while (!finished && i % 3 != remainder) Monitor.Wait(locker);
if (finished) break;
Console.WriteLine($"Worker #{remainder} produced {i}");
Monitor.PulseAll(locker);
} while (++i < 20);
}
finally { finished = true; Monitor.PulseAll(locker); }
}
})).ToArray();
Array.ForEach(threads, t => t.Start());
Array.ForEach(threads, t => t.Join());
创建了三个工作线程,由 remainder
参数标识,取值 0、1 和 2。每个工作线程负责生成其模 3 等于余数的数字。
int i
是循环变量,bool finished
是一个标志,当任何一个工人完成时,它会变成 true
。此标志确保在任何工作人员出错的情况下,其他工作人员不会死锁。
每个工人都进入一个临界区,该临界区包含一个do-while
循环,即数字生成和递增循环。在发出数字之前,它必须等待轮到它。 i % 3 == remainder
时轮到它了。否则它 Wait
s。轮到它时,它发出数字,它递增 i
,它 Pulse
是所有等待的工作人员,并继续下一次迭代。当循环结束时,它是 Pulse
释放锁前的最后一次。
选择了 PulseAll
而不是 Pulse
,因为我们不知道等待队列中的下一个工人是否是当前 i
的正确工人,所以我们把他们都叫醒。
输出:
Worker #0 produced 0
Worker #1 produced 1
Worker #2 produced 2
Worker #0 produced 3
Worker #1 produced 4
Worker #2 produced 5
Worker #0 produced 6
Worker #1 produced 7
Worker #2 produced 8
Worker #0 produced 9
Worker #1 produced 10
Worker #2 produced 11
Worker #0 produced 12
Worker #1 produced 13
Worker #2 produced 14
Worker #0 produced 15
Worker #1 produced 16
Worker #2 produced 17
Worker #0 produced 18
Worker #1 produced 19
注意:这个答案 1st revision 中的示例是有问题的,因为它创建了一个初始的忙等待阶段,直到所有工作人员都准备好了。