C# 信号量是如何从另一个线程释放的?

C# 信号量是如何从另一个线程释放的?,c#,multithreading,semaphore,C#,Multithreading,Semaphore,据我所知,线程可以在信号量上释放,而无需首先使用WaitOne获取锁 因此,如果我们有线程A、B和C以及一个信号量,A和B调用WaitOne,获得一个锁并开始处理它们的事务。 线程C出现,只需在信号量上调用Release 这将使信号量的计数增加1。这是否意味着信号量将终止A或B,或者只允许第三个线程获取锁并在其池中有3个线程,即使最大值为2?您可以将信号量视为阻塞队列的一种特殊情况:信号量的“计数”是队列中的项目数,但这些项目本身不包含任何信息。正如任何线程都可以将项目放入阻塞队列,任何线程都可

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

因此,如果我们有线程A、B和C以及一个信号量,A和B调用
WaitOne
,获得一个锁并开始处理它们的事务。
线程C出现,只需在信号量上调用
Release


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

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

请考虑

var semaphore = new SemaphoreSlim(2);
这意味着此时的信号量只有2个执行时隙,但您必须记住,它只是执行时隙的初始数目(对于并发授予的请求)

因此,如果我们将A、B、C线程生成一个带有2个执行槽的信号量,那么前两个线程将被执行,C线程将排队,直到代码中的其他人向信号量发出信号,再添加一个执行槽是可以的

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


一些技术示例: (正如我在@dmitri nesteruk的课程中看到的)

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

每次线程想要执行时,它都会询问信号量是否有可用的执行槽(使用
CurrentCount>0
),如果为true-如果不进入队列,请随意执行

使信号量如此混乱的是,
CurrentCount
值可以减小或增大

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

  • 每次发布
    版本(1)
    时,其增加一个(或多个) 在代码中的其他地方调用,这意味着还有一个 可用的执行槽,因此信号量中的第一个线程位于 正在执行队列(它不会终止其他队列)

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


信号量不是活动对象;它不控制任何代码的运行。程序员有责任以合理的方式使用它。如果这意味着增加互斥,那是另一个问题——一个简单的计数信号量不能解决这个问题。线程C恶意释放了一个它不拥有的资源,这是有缺陷的。监视器是一个更高级别的抽象,没有这个问题,这就是为什么.NET代码中的原始信号量非常少的原因。对于更简单的场景,当只需要线程安全计数器时,会有
联锁的
。在执行线程之前,重置两个信号量。然后在末尾的线程内设置信号量。所以C除了等待两个信号量被设置外,什么也不做。@jdweng,让我们假设我没有遵循好的编程实践,试图为互联网上关于这类事情的特定问题提供一个理论场景。@JeroenMostert,这是否意味着我在这个问题上的假设是正确的?是的,但和通常的情况一样,说服自己的最好方法是制作一个演示问题的小玩具应用程序(像LINQPad这样的工具使这变得非常容易)。我认为这比互联网上任何随机的人的评论都要好(当然更具洞察力)。虽然显示线程问题有时可能很棘手(有时没有任何完全不可能的问题),但出于演示目的,在必要的地方插入一些
。睡眠通常会起作用。你的答案很好,但Jeroen Mostert在评论中给出了回答我问题的关键点,这是“信号量不是活动对象”,也就是说,信号量不控制代码的执行,它只是一个简单的同步构造,让事情通过它,但之后它们会做什么-它不在乎。因为最初我认为它控制它所传递的任何线程的执行,就像监视器一样(在某种意义上,监视器非常关心进入它的线程发生了什么,因为它需要释放它)@ShaharShokrani,这是大多数编程语言和库中大多数同步对象的典型特征:它们只起到门的作用,在满足某些条件之前阻止线程通过。为了防止数据结构损坏,程序员必须设置一个门,然后它就成为程序员的责任ty以确保任何代码都不会修改数据,除非数据已通过闸门。
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