线程安全:锁定与参考

lol*_*dez 5 c# multithreading locking multicore multiprocessing

我有一个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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

参考

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
    }
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*rvy 8

没有锁,这是不安全的.在此上下文中,复制对列表的引用并没有真正为您做任何事情.当你迭代时,你正在迭代的列表仍然很可能在另一个线程中被变异,从而导致各种可能的错误.

  • @alykins正确的方法是锁定具有情况所需范围的对象的对象,而不是你永远不应该锁定不属于`object`类型的对象.这是因为如果你在任何给定的时间都不知道谁可能锁定一个对象你可以创建死锁,当锁定对象的范围很小时,它会更容易推断程序的状态.对象的类型是`object`还是`List <string>`或者其他什么都不是真正相关的. (2认同)