我有一个C#程序,它有一个列表,可以在单独的线程中进行写入和读取。写入是由用户发起的,并且可以在任意时间点更改数据。读取在一个常数循环中运行。读取是否丢失任何给定循环中的数据并不重要,只要它接收的数据是有效的,并且在未来的循环中得到新的数据。
在考虑了ConcurrentBag之后,出于各种原因(简单性就是其中之一),我决定使用锁。在实现锁之后,一位同事向我提到,使用临时引用来指向内存中的旧列表也同样有效,但我担心如果同时进行新的分配和引用分配会发生什么情况。
Q:线程下面的临时引用示例是否安全?
更新:用户输入提供DoStuff()中使用的字符串列表。您可以将这些字符串看作常量的定义,因此,对于未来的循环,字符串需要持久化。它们不会在DoStuff()中被删除,只会被读取。UserInputHandler是唯一将更改此列表的线程,而DoStuff()则是唯一将从该列表中读取的线程。没有其他人能接触到它。
此外,我知道并发命名空间,并在其他项目中使用了其中的大多数集合,但是,由于它们添加的额外代码复杂性(即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
}
}发布于 2014-06-06 18:47:13
没有锁这是不安全的。在这种情况下,复制对列表的引用并不能真正为您做任何事情。当前正在迭代的列表仍然很有可能在迭代时在另一个线程中发生变异,从而导致各种可能的错误。
发布于 2014-06-06 19:04:21
我想你要找的是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
}
}发布于 2014-06-06 19:05:08
无锁是危险的,不能携带。不要这样做。如果您需要阅读如何进行无锁操作,则可能不应该这样做。。
我想我没能理解这个问题。我有一种奇怪的印象,那就是名单只被添加到或仅仅是最近的版本中,这才是最重要的。我不知道当他显式地显示一个"clear()“调用时是如何做到这一点的。
我为混乱而道歉。
这段代码是有争议的,请自己承担风险,但我确信它应该在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的编写器,则需要找到同步它们的方法。
https://stackoverflow.com/questions/24088662
复制相似问题