【问题标题】:How does a semaphore release from another thread?信号量如何从另一个线程释放?
【发布时间】:2019-01-28 11:34:23
【问题描述】:

据我所知,线程可以在信号量上释放而无需先使用WaitOne 获取锁。

所以,如果我们有线程 A、B 和 C 以及一个信号量,A 和 B 调用 WaitOne,获取锁并开始执行它们的业务。
随之而来的是线程 C,它只在信号量上调用 Release

这应该将信号量的计数加 1。这是否意味着信号量将终止 A 或 B,或者只允许第三个线程获取锁并在其池中拥有 3 个线程,即使最大值为 2?

【问题讨论】:

  • 信号量不是活动对象;它不管理任何代码的运行。以有意义的方式使用它是程序员的责任。如果这意味着增加互斥,那是一个单独的问题——一个简单的计数信号量并不能解决这个问题。线程 C 流氓释放它不拥有的资源,是错误的。监视器是没有这个问题的更高级别的抽象,这就是为什么 .NET 代码中的原始信号量相当少的原因。对于更简单的场景,当只需要线程安全的计数器时,有Interlocked
  • 在你执行线程之前你重置了两个信号量。然后在最后的线程内设置信号量。所以 C 除了等到两个信号量都设置好之外什么都不做。
  • @jdweng,让我们暂时假设我没有遵循良好的编程实践来为 Internet 站点上有关此类事情的特定问题提供理论场景。
  • @JeroenMostert,这是否意味着我对问题的假设是否正确?
  • 是的,但是像往常一样,说服自己的最好方法是制作一个演示问题的小玩具应用程序(LINQPad 之类的工具使这变得非常容易)。我认为这比互联网上任何随机人的 cmets 都要好(当然更有洞察力)。虽然显示线程问题有时可能很棘手(有时完全不可能出现任何问题),但出于演示目的,在必要时插入几个.Sleeps 通常会解决问题。

标签: c# multithreading semaphore


【解决方案1】:

考虑

var semaphore = new SemaphoreSlim(2);

表示此时的信号量只有 2 个执行槽,但你要记住它只是执行槽的初始数量(用于并发授予的请求) )。

所以,如果我们将 A、B、C 线程派生到具有 2 个执行槽的信号量中,则前两个线程将被执行,C 线程将排队,直到代码中的其他人发出信号量增加一个执行槽。

当有人说可以执行队列中的下一个线程时,不管其他线程如何,都会执行 C。


一些技术示例:

(正如我在 @dmitri-nesteruk 的课程中​​看到的)

可用个执行槽的总数由CurrentCount表示。

每当一个线程想要被执行时,它都会询问信号量是否有可用的执行槽(使用CurrentCount > 0),如果为真 - 随意让自己执行,如果没有进入队列。

使信号量如此混乱的原因是CurrentCount 的值可以减少或增加。

  • 每次Wait() 被一个 线程,这意味着少了一个可用的执行槽 并且正在执行一个线程。

  • 每次Release(1) 增加一(或更多) 在代码的其他地方调用,这意味着还有一个 可用的执行槽所以第一个线程在信号量里面 queue 正在执行(不会终止其他队列)。

在本例中,我们生成了 3 个线程,但只有前两个线程将被执行,直到有人对信号量说他可以通过将 CurrentCount 增加为 Release(1) 来释放另一个执行槽。

for (int i = 0; i < 3 ; i++)
{
    Task.Factory.StartNew(() => 
    {
        Console.WriteLine($"Spawning task: {Task.CurrentId}");
        semaphore.Wait(); //CurrentCount--
        Console.WriteLine($"Executing task: {Task.CurrentId}");
    });
}


while (semaphore.CurrentCount <= 2)
{
    Console.ReadKey();
    Console.WriteLine("Key pressed");
    semaphore.Release(1); //CurrentCount++
}

输出:

Spawning task: A
Spawning task: B
Spawning task: C
Executing task: A
Executing task: B
.....
Key pressed
Executing task: C

【讨论】:

  • 你的回答很好,但回答我问题的关键点是在 Jeroen Mostert 的评论中给出的,它是“信号量不是活动对象”,即信号量没有任何作用管理代码的执行,它只是一个简单的同步结构,它可以让事情过去,但之后他们会做什么 - 它不在乎。因为最初我认为它控制着它通过的任何线程的执行,就像一个监视器(从某种意义上说,监视器非常关心进入它的线程会发生什么,因为它需要释放它)。
  • @ShaharShokrani,这是大多数编程语言和库中大多数同步对象的典型特征:它们仅用作阻止线程在满足某些条件之前通过的门。为了保护数据结构不被破坏,程序员必须设置一个门,然后程序员有责任确保除了通过门后没有代码修改数据。
【解决方案2】:

您可以将信号量视为阻塞队列的特例:信号量的“计数”是队列中的项目数,但项目本身不包含任何信息。就像任何线程都可以将一个项目放入阻塞队列,任何线程都可以取出一个项目一样,任何线程都可以增加或减少信号量的计数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-28
    • 1970-01-01
    相关资源
    最近更新 更多