C# 使用信号量LIM限制对共享变量的访问是否保证所有写入都可见? 总结
我有一个类,它使用C# 使用信号量LIM限制对共享变量的访问是否保证所有写入都可见? 总结,c#,multithreading,asynchronous,semaphore,C#,Multithreading,Asynchronous,Semaphore,我有一个类,它使用lock提供对私有字段的线程安全访问。然而,出于下面详细说明的原因,我正在考虑切换到使用SemaphoreSlim来实现线程安全。我知道,如果我使用锁(\u lock)(正如我目前所做的那样)包围对该字段的所有访问,我可以保证对该字段的写入将是原子的,并且所有线程都将看到最近写入的值()。我想知道我是通过信号量lim得到这两种保证,还是只得到关于原子写入的保证 细节 我有一个不久前编写的接口,用于表示可用于与外部设备通信的服务: public interface ICommun
lock
提供对私有字段的线程安全访问。然而,出于下面详细说明的原因,我正在考虑切换到使用SemaphoreSlim
来实现线程安全。我知道,如果我使用锁(\u lock)
(正如我目前所做的那样)包围对该字段的所有访问,我可以保证对该字段的写入将是原子的,并且所有线程都将看到最近写入的值()。我想知道我是通过信号量lim
得到这两种保证,还是只得到关于原子写入的保证
细节
我有一个不久前编写的接口,用于表示可用于与外部设备通信的服务:
public interface ICommunicationService : IDisposable
{
event EventHandler<ReceivedMessageEventArgs> MessageReceived;
void SendMessage(GenericMessage message);
void Start();
}
尝试将此代码切换到异步实现时,我注意到我无法在lock
语句中等待任务。根据异步大师斯蒂芬·克利里(Stephen Cleary)的说法,异步方法是使用信号量lim
基于这篇文章,我将我的SendMessage
实现更改为:
public virtual async Task SendMessage(GenericMessage message)
{
CheckDisposed();
if (CommunicationState != CommunicationState.Up)
{
throw new InvalidOperationException("Message cannot be sent as communication is not established.");
}
// _mutex = new SemaphoreSlim(0, 1)
await _mutex.WaitAsync().ConfigureAwait(false);
try
{
await _currentDelegate.SendMessage(message);
}
catch (Exception)
{
TerminateCommunication();
}
finally
{
_mutex.Release();
}
}
但我想知道的是,我是否保证任何给定的线程在执行wait\u currentDelegate.SendMessage(message)
时都会看到\u currentDelegate
的最新值,或者我是否需要使用另一个构造来确保其他线程可以立即看到写操作
特别是,此类还有另一个方法TryRestartCommunication
:
private bool TryRestartCommunication(bool initialStart)
{
// ...
lock (_lock)
{
// ...
try
{
_currentDelegate = _factory();
_currentDelegate.Start();
_currentDelegate.MessageReceived += MessageReceived;
CommunicationState = CommunicationState.Up;
return true;
}
// ...
}
}
如果我重新实现此方法以锁定信号量,并让某个线程A调用TryRestartCommunication()
,然后在线程B调用SendMessage()
,我是否可以保证线程B将看到线程A设置的\u currentDelegate
的新值
如果不是的话,一个合适的解决方案是只将\u currentDelegate
设置为易失性,还是使用互锁
来更新其值
编辑
投票结果很接近,因为这个问题显然不够清楚。让我尽可能清楚地说明一下:如果我从使用
lock
保护关键区域切换到SemaphoreSlim
,那么我是否需要将共享字段标记为volatile
(或类似内容),以确保相同级别的线程安全性?如果您从使用lock()保护关键区域/数据切换到对于SemaphoreSlim(1),您将获得相同级别的线程安全性
我们已经做了好几次了,从来没有遇到过一个数据保护错误
但是,请注意,
wait xxx.configurewait(false)之后
您可以在不同的同步上下文(线程)和方法wait\u currentDelegate.SendMessage(消息)中结束代码>可能不适合它。例如,如果该方法访问Webform UI控件。您不能使用Interlocked
更改委托,并且不清楚信号量有什么问题。有关锁获取模式,请参阅,这是您必须对信号量执行的操作。@Sinatr“您不能使用Interlocked更改委托”不确定您的意思,我添加了Interlocked.Exchange(ref _currentDelegate,_factory())
到异步版本的TryRestartCommunication
,编译器不会抱怨。“不清楚信号量有什么问题。”我并不是说一定有问题,我只是想确定它提供的保证。也可以使用Interlocked
来显示它们,FWIW。我的意思是,一旦您获得所有权,您必须运行多个语句,这在Interlocked
中是不可能的,但在信号量中是可能的(我假设您最多使用1个所有者)@Sinatr如果你看到我的帖子,我已经在使用信号量限制访问我的关键部分。我要问的是,获取信号量是否涉及与lock
相同的内存屏障操作,以确保在我离开临界区时,其他线程可以立即看到我在临界区内更改的任何共享状态。如果没有,我相信我需要将联锁
与我的信号量结合使用,因为没有内存障碍,任何框架或操作系统都无法实现同步对象。这种偶然的障碍比大多数程序员想象的要普遍得多。但除此之外,这个常见建议的理由是:如果您认为您需要一个屏障,那么您可能应该使用lock。
public virtual async Task SendMessage(GenericMessage message)
{
CheckDisposed();
if (CommunicationState != CommunicationState.Up)
{
throw new InvalidOperationException("Message cannot be sent as communication is not established.");
}
// _mutex = new SemaphoreSlim(0, 1)
await _mutex.WaitAsync().ConfigureAwait(false);
try
{
await _currentDelegate.SendMessage(message);
}
catch (Exception)
{
TerminateCommunication();
}
finally
{
_mutex.Release();
}
}
private bool TryRestartCommunication(bool initialStart)
{
// ...
lock (_lock)
{
// ...
try
{
_currentDelegate = _factory();
_currentDelegate.Start();
_currentDelegate.MessageReceived += MessageReceived;
CommunicationState = CommunicationState.Up;
return true;
}
// ...
}
}