信号量 - 初始计数的用途是什么?

145

http://msdn.microsoft.com/en-us/library/system.threading.semaphoreslim.aspx

创建一个信号量时,我需要提供初始计数和最大计数。MSDN指出,初始计数是-

可同时授予的信号量请求的初始数量。

而它也指出了最大计数:

可同时授予的信号量请求的最大数量。

我可以理解,最大计数是同时访问资源的线程的最大数目,但初始计数有什么用呢?

如果我使用初始计数为0,最大计数为2创建一个信号量,则我的线程池线程都无法访问该资源。如果我将初始计数设置为1,最大计数设置为2,则只有一个线程池线程可以访问该资源。只有当我将初始计数和最大计数均设置为2时,才能同时让两个线程访问该资源。因此,我真的很困惑初始计数的意义是什么?

SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0, 2); //all threadpool threads wait
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 2);//only one thread has access to the resource at a time
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(2, 2);//two threadpool threads can access the resource concurrently
10个回答

121

所以,我真的很困惑初始计数的意义是什么?

这里有一个重要的点可能会有所帮助,就是Wait操作会减少信号量计数,而Release操作则会增加它。

initialCount是立即允许的资源访问次数。换句话说,在信号量实例化后,它是可以调用Wait而不被阻塞的次数。

maximumCount是信号量可以获得的最高计数。假设initialCount计数为零,则它是可以调用Release而不抛出异常的次数。如果将initialCount设置为与maximumCount相同的值,则在信号量实例化后立即调用Release将会抛出异常。


28
非常有帮助!我之前一直对信号量的理解存在误区,认为initialCount代表初始被阻塞的资源数量,而不是可以立即使用的资源数量。谢谢你。 - Philip Tenn
10
@PhilipTenn,我同意 - 这方面的文档不够清晰。 - BlueStrat
我同意,他们应该更改那个变量名或者更新文档。 - IronHide
@Sandbox 我个人认为您应该接受这个答案,因为它真正解释了initialCount参数的含义。 - Michał Turczyn
说实话,我还是不太明白。在什么情况下,我会在不调用等待(Wait)的情况下调用释放(Release)呢? - undefined

110

当初始数字设置为0时,所有线程都会等待您增加“CurrentCount”属性。您可以使用Release()或Release(Int32)来完成此操作。

Release(...) - 将增加信号量计数器

Wait(...) - 将减少它

您无法将计数器(“CurrentCount”属性)增加到大于初始化中设置的最大计数。

例如:

SemaphoreSlim^ s = gcnew SemaphoreSlim(0,2); //s->CurrentCount = 0
s->Release(2); //s->CurrentCount = 2
...

s->Wait(); //Ok. s->CurrentCount = 1
...

s->Wait(); //Ok. s->CurrentCount = 0
...

s->Wait(); //Will be blocked until any of the threads calls Release()

1
您的代码将在答案中呈现更好,而不是作为评论。 - ChrisF
32
这可能是我第5次得出相同的答案,因为构造函数的文档总是让我对应该设置哪些值感到困惑。干杯。 - BlueStrat

16

您希望一次有多少个线程可以同时访问资源?将初始计数设置为该数字。如果该数字在程序的生命周期内永远不会增加,请将最大计数设置为该数字。这样,如果您在释放资源方面存在编程错误,程序将崩溃并提示您。

(有两个构造函数:一个仅接受初始值,另一个还接受最大计数。请使用适当的构造函数。)


8
一般情况下,当使用SemaphoreSlim作为节流器时,initialCountmaxCount都具有相同的值。
var semaphore = new SemaphoreSlim(maximumConcurrency, maximumConcurrency);

...并且在此模式中使用了信号量

await semaphore.WaitAsync(); // or semaphore.Wait();
try
{
    // Invoke the operation that must be throttled
}
finally
{
    semaphore.Release();
}
initialCount 配置了最大并发策略,而 maxCount 则确保不会违反该策略。如果省略第二个参数(即 maxCount),只要没有 bug,你的代码依然能够正常工作。但是如果有 bug,每个 WaitAsync 可能会被多个 Release 跟随,那么 maxCount 将帮助检测这个 bug,以免它出现在你程序的发布版本中。此类 bug 会表现为 SemaphoreFullException,希望它能在预发布版本的测试期间被发现,因此您可以在其造成实际损失之前跟踪和消除它(在生产环境中违反了最大并发策略之前)。

如果省略了 maxCount 参数,则其默认值为 Int32.MaxValue源代码)。


1
优秀的答案,明确无误! - Keith Bluestone

4

如果您希望在某段时间内不允许任何线程访问您的资源,您可以将初始计数器设置为0。当您希望在创建信号量后立即授予访问权限时,可以将初始计数器的值设置为最大计数器的值。例如:

hSemaphore = CreateSemaphoreA(NULL, 0, MAX_COUNT, NULL) ;

//Do something here
//No threads can access your resource

ReleaseSemaphore(hSemaphore, MAX_COUNT, 0) ;

//All threads can access the resource now

根据MSDN文档的引用,“ReleaseSemaphore”的另一个用途是在应用程序初始化期间。 应用程序可以创建初始计数为零的信号量。 这将设置信号量的状态为未发出信号并阻止所有线程访问受保护的资源。 当应用程序完成其初始化时,它使用“ReleaseSemaphore”将计数增加到其最大值,以允许对受保护资源的正常访问。”


抱歉,我用C++给你举了例子,但是我可以解决你的疑问。 - Abhineet
1
关于初始化的引用非常有帮助,尽管其他答案解释了SemaphoreSlim的工作原理,但这是唯一一个提供了这种有些晦涩功能非常有用的示例用例的答案。 - shelbypereira

2

这样,当当前线程创建信号量时,它可以从一开始就声明一些资源。


那么,您的意思是当我想让两个工作线程访问资源时,我应该更改初始计数吗? - Sandbox
不是计数器在声明当前线程。如果您不希望当前线程声明任何访问权限,请传递0或使用一个参数的重载。 - Emond

2
想象一下:
- `initialCount` 是“并行度”(可以进入的线程数) - `maxCount` 确保您不会释放超过应该释放的数量
例如,假设您希望并发度为“1”(一次只进行一项操作)。但是由于代码中的某个错误,您释放了信号量两次。现在您有了并发度为二!
但是,如果您设置了 `maxCount`,它将不允许这种情况,并抛出异常。

0

maxCount 是您要允许的并发线程数。

然而,当您开始限制时,您可能已经知道有一些活动线程,因此您想告诉它“嘿,我想要6个并发线程,但我已经有4个了,所以现在我只想让你再允许2个”,因此您将initialCount设置为2,maxCount设置为6。

SemaphoreSliminitialCount的限制是它不能是负数,因此您不能说“嘿,我想要最多6个并发线程,但我目前有10个,所以在您允许另一个线程进入之前,请释放5个。” 这意味着initialCount为-4。对于这个问题,您需要使用第三方软件包,例如SemaphoreSlimThrottling(请注意,我是SemaphoreSlimThrottling的作者)。


不,initialCount 是可以进入的并发线程数量。 - Alex from Jitbit
@AlexfromJitbit,这不对。如果你从 initialCount: 0maxCount: 2 开始,这意味着你最多可以有两个并发线程,但是在你第一次释放之前不能有任何线程进入。如果你 Release(2),那么你立即可以允许两个线程进入,尽管 initialCount 被设置为 0。 - Mark Cilia Vincenti

-1

信号量可以用来保护资源池。我们使用资源池来重复使用那些创建成本高昂的东西,比如数据库连接。

因此,初始计数是指在某个过程开始时池中可用资源的数量。当您在代码中读取initialCount时,您应该考虑到您投入了多少前期工作来创建这个资源池。

我真的很困惑初始计数的意义是什么?

Initial count = Upfront cost

因此,根据您的应用程序的使用情况,此值可能会对应用程序的性能产生巨大影响。它不仅仅是一些任意的数字。

您应该仔细考虑您正在创建什么,它们的创建成本有多高以及您需要多少立即使用。您应该能够绘制出此参数的最佳值,并且应该考虑使其可配置,以便您可以根据执行时间调整进程的性能。


-1

正如MSDN在备注部分所解释的:

如果initialCount小于maximumCount,则效果与当前线程调用WaitOne(maximumCount减initialCount)次相同。如果您不想为创建信号量的线程保留任何条目,请对maximumCount和initialCount使用相同的数字。

因此,如果初始计数为0且最大值为2,则就像主线程已经调用了两次WaitOne一样,因此我们已经达到容量上限(信号量计数现在为0),没有线程可以进入Semaphore。同样,如果初始计数为1且最大值为2,则已经调用了一次WaitOnce,只有一个线程可以进入,然后再次达到容量上限,以此类推。

如果将0用于初始计数,我们始终可以调用Release(2)将信号量计数增加到最大值,以允许最大数量的线程获取资源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接