我有一个C#程序,它有一个列表,可以在不同的线程中进行写入和读取。写入是用户启动的,可以在任何随机时间点更改数据。读取以恒定循环运行。无论读取是否在任何给定循环中丢失数据都无关紧要,只要它接收的数据有效并且在未来循环中获得新数据。
在考虑ConcurrentBag之后,我出于各种原因决定使用锁(简单就是其中之一)。在实现锁之后,一位同事向我提到,使用临时引用指向内存中的旧List也可以正常工作,但我担心如果新的赋值和引用赋值同时发生会发生什么。
问:线程安全下面的临时参考示例是什么?
更新:用户输入提供DoStuff()中使用的字符串列表。您可以将这些字符串视为常量的定义,因此需要为将来的循环保留字符串。它们不会在DoStuff()中删除,只能读取。 UserInputHandler是唯一一个将更改此列表的线程,DoStuff()是唯一将从此列表中读取的线程。没有其他人可以访问它。
此外,我知道Concurrent命名空间并在其他项目中使用了大部分集合,但是,由于他们添加了额外的代码复杂性,我选择不在这里使用它们(即ConcurrentBag不会有一个简单的Clear()函数等)。在这种情况下,一个简单的锁就足够了。问题只是下面的第二个例子是否是线程安全的。
锁定
static List<string> constants = new List<string>();
//Thread A
public void UserInputHandler(List<string> userProvidedConstants)
{
lock(items)
{
items.Clear();
foreach(var constant in userProvidedConstants)
{
constants.Add(constant);
}
}
}
//Thread B
public void DoStuff()
{
lock(items)
{
//Do read only actions with items here
foreach(var constant in constants)
{
//readonly actions
}
}
}
参考
static List<string> constants = new List<string>();
//Thread A
public void UserInputHandler(List<string> userProvidedConstants)
{
lock(items)
{
items = new List<string>();
foreach(var constant in userProvidedConstants)
{
constants.Add(constant);
}
}
}
//Thread B
public void DoStuff()
{
var constantsReference = constants;
//Do read only actions with constantsReference here
foreach(var constant in constantsReference)
{
//readonly actions
}
}
答案 0 :(得分:8)
没有锁,这是不安全的。在此上下文中,复制对列表的引用并不能为您做任何事情。当你迭代它时,你正在迭代的列表仍然很可能在另一个线程中被变异,导致各种可能的不良。
答案 1 :(得分:0)
我认为您正在寻找的是BlockingCollection
。查看以下链接以开始使用它:
http://msdn.microsoft.com/en-us/library/dd997371%28v=vs.110%29.aspx
这是使用BlockingCollection的一个例子。 ThreadB不会开始枚举BlockingCollection直到有可用项目,并且当项目用完时,它将停止枚举,直到有更多项目可用(或直到IsCompleted
属性返回true)
private static readonly BlockingCollection<int> Items = new BlockingCollection<int>();
//ThreadA
public void LoadStuff()
{
Items.Add(1);
Items.Add(2);
Items.Add(3);
}
//ThreadB
public void DoStuff()
{
foreach (var item in Items.GetConsumingEnumerable())
{
//Do stuff here
}
}
答案 2 :(得分:-1)
免费锁定是危险的,不便携。不要这样做。如果您需要了解如何进行无锁操作,您可能不应该这样做。
我想我错过了解这个问题。我的奇怪印象是,该列表只是被添加到或者只有最新版本才是最重要的。当他明确地表明&#34; clear()&#34;时,不知道我是怎么做到的。调用
我为这种困惑道歉。
此代码存在争议,使用风险自负,但我确信它应该适用于x86 / x64,但没有关于ARM的线索
你可以做这样的事情
//Suggested to just use volatile instead of memorybarrier
static volatile T _MyList = new ReadOnlyList<T>();
void Load(){
T LocalList = _MyList.Copy();
LocalList.Add(1);
LocalList.Add(2);
LocalList.Add(3);
_MyList = LocalList.ReadOnly(); //Making it more clear
}
DoStuff(){
T LocalList = _MyList;
foreach(t tmp in LocalList)
}
这应该适用于繁重的读取工作负载。如果您有多个修改_MyList的编写器,您需要找到一种方法来同步它们。