线程安全:锁定与参考

时间:2014-06-06 18:44:35

标签: c# multithreading locking multiprocessing multicore

我有一个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
    }
}

3 个答案:

答案 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的编写器,您需要找到一种方法来同步它们。