ConcurrentDictionary 使用安全吗?
Is ConcurrentDictionary safe to use?
我在不同的线程中有三个不同的代码 运行。
线程任务 1:从设备读取数据并将其写入 ConcurrentDictionary
。
线程任务2:将ConcurrentDictionary
中的数据作为单独的文件写入计算机。
我在论坛上看过很多帖子,说 concurrentdictionary 对于单独的线程是安全的。我还读到有锁定情况。其实我脑子里不止一个问号
concurrentdictionary需要加锁吗?在哪些情况下需要锁定?如果需要加锁怎么办?以下方式使用会导致什么问题?
线程代码1:每秒都有数据进来
public void FillModuleBuffer(byte[] buffer, string IpPort)
{
if (!CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
CommunicationDictionary.dataLogList.TryAdd(IpPort, buffer);
}
}
线程代码 2: 下面的代码在定时器中工作。计时器持续时间为 200 毫秒。
if (CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
using (stream = File.Open(LogFilename, FileMode.Append, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(CommunicationDictionary.dataLogList[IpPort]);
writer.Flush();
writer.Close();
CommunicationDictionary.dataLogList.TryRemove(IpPort,out _);
}
}
}
注意:为清楚起见,对代码进行了简化。
注2:我之前用的是Dictionary
。我遇到了一个非常不同的问题。处于活动状态时,2-3 小时后,我收到错误消息,即数组超出索引范围,即使 Dictionary
.
中没有数据
示例代码应该是线程安全的,但它显示了对如何使用并发字典的误解。例如:
if (!CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
CommunicationDictionary.dataLogList.TryAdd(IpPort, buffer);
}
这恰好可行,因为只有一个线程添加到字典中,但由于有单独的语句,字典可能会在它们之间发生变化。如果您查看 TryAdd 的文档,您会发现如果密钥已经存在,它将 return false。所以不需要ContainsKey
。有很多不同的方法,目的是同时做多件事,以确保整个操作是原子的。
与阅读线程相同。对 concurrentDictionary 的所有访问都应替换为对 TryRemove
的一次调用
if (CommunicationDictionary.dataLogList.TryRemove(IpPort,out var data))
{
using (stream = File.Open(LogFilename, FileMode.Append, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(data);
writer.Flush();
writer.Close();
}
}
}
请注意,这将保存一些数据块,并丢弃其他数据块,没有任何硬性保证将保存或不保存哪些块。这可能是预期的行为,但对于确保保存所有数据的队列来说更常见。一个典型的实现会用一个或多个生产线程和一个消费线程来包装 concurrentQueue in a blockingCollection。这避免了对单独计时器的需要。
我在不同的线程中有三个不同的代码 运行。
线程任务 1:从设备读取数据并将其写入 ConcurrentDictionary
。
线程任务2:将ConcurrentDictionary
中的数据作为单独的文件写入计算机。
我在论坛上看过很多帖子,说 concurrentdictionary 对于单独的线程是安全的。我还读到有锁定情况。其实我脑子里不止一个问号
concurrentdictionary需要加锁吗?在哪些情况下需要锁定?如果需要加锁怎么办?以下方式使用会导致什么问题?
线程代码1:每秒都有数据进来
public void FillModuleBuffer(byte[] buffer, string IpPort)
{
if (!CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
CommunicationDictionary.dataLogList.TryAdd(IpPort, buffer);
}
}
线程代码 2: 下面的代码在定时器中工作。计时器持续时间为 200 毫秒。
if (CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
using (stream = File.Open(LogFilename, FileMode.Append, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(CommunicationDictionary.dataLogList[IpPort]);
writer.Flush();
writer.Close();
CommunicationDictionary.dataLogList.TryRemove(IpPort,out _);
}
}
}
注意:为清楚起见,对代码进行了简化。
注2:我之前用的是Dictionary
。我遇到了一个非常不同的问题。处于活动状态时,2-3 小时后,我收到错误消息,即数组超出索引范围,即使 Dictionary
.
示例代码应该是线程安全的,但它显示了对如何使用并发字典的误解。例如:
if (!CommunicationDictionary.dataLogList.ContainsKey(IpPort))
{
CommunicationDictionary.dataLogList.TryAdd(IpPort, buffer);
}
这恰好可行,因为只有一个线程添加到字典中,但由于有单独的语句,字典可能会在它们之间发生变化。如果您查看 TryAdd 的文档,您会发现如果密钥已经存在,它将 return false。所以不需要ContainsKey
。有很多不同的方法,目的是同时做多件事,以确保整个操作是原子的。
与阅读线程相同。对 concurrentDictionary 的所有访问都应替换为对 TryRemove
的一次调用if (CommunicationDictionary.dataLogList.TryRemove(IpPort,out var data))
{
using (stream = File.Open(LogFilename, FileMode.Append, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(data);
writer.Flush();
writer.Close();
}
}
}
请注意,这将保存一些数据块,并丢弃其他数据块,没有任何硬性保证将保存或不保存哪些块。这可能是预期的行为,但对于确保保存所有数据的队列来说更常见。一个典型的实现会用一个或多个生产线程和一个消费线程来包装 concurrentQueue in a blockingCollection。这避免了对单独计时器的需要。