锁定集合中的对象而不是锁定整个集合是否安全?

时间:2016-08-26 11:02:21

标签: c# multithreading

我想知道,假设我将它作为类实例级变量:

Dictionary<int,Person> data = new Dictionary<int,Person>();

在一个主题中(可能更多):

lock(lockObj)
{
   // Access the object for writing. Locking to sync from other threads.
   Person p = data[1];
   p.Name = "Something";
}

然后在另一个主题中:

foreach(KeyValuePair<int,Person> kvp in data)
{
   // Enumerating to access the object's value as for read.
   console.WriteLine(kvp.Name);
}

从线程安全的角度来看是否存在任何问题?

2 个答案:

答案 0 :(得分:4)

Dictionary是线程安全的,只要没有修改它,所以对于你的具体例子,不需要锁定,因为你没有修改字典本身。

然而,您正在修改里面的字典。但是再次针对您的具体示例,锁定无法实现任何效果,因为引用赋值(在您执行p.Name = "Something";的情况下发生)是一个原子操作(假设Name实际上不是执行某些操作的属性除了简单的参考作业之外)。

因此,锁定不会改变您发布的代码中的任何行为。

如果您正在进行非原子更改(例如赋值给double)或对对象中的多个字段进行更改,那么您应该围绕这些更改以及访问受影响字段的任何内容进行锁定。

例如,如果你有一个具有int属性A和B的类,它只能作为原子操作进行更改,那么你必须编写这样的代码来改变OR访问字段:

// Change fields.

lock (locker)
{
    A = newValueForA;
    B = newValueForB;
}

// Access fields.

int safeCopyOfA;
int safeCopyOfB;

lock (locker)
{
    safeCopyOfA = A;
    safeCopyOfB = B;
}

// Use safeCopyOfA and safeCopyOfB

实际上,您通常会将这些字段包装在单个不可变类中,并将其作为单个属性而不是两个相互依赖的属性公开,以简化代码并使其更加健壮。

另请注意,如果您要对字典本身进行更改(例如添加或删除项目),那么您应该锁定对字典的所有读写访问权限(使用相同的锁定对象)。

或使用ConcurrentDictionary

答案 1 :(得分:1)

在你的情况下有两个对象:集合实例和项目实例。

您正在同步对集合的访问权限。只有在修改集合(添加,删除,清除,data)时,才能在访问new的任何地方执行此操作。如果收集没有变化 - 您不必同步访问它。

项目实例也可能需要同步(如果多个线程可以获取并使用它),遵循与之前完全相同的逻辑。

与项目属性等相同。始终尝试考虑谁将访问它以及如何准确。开始使用并发集合可能是值得的,因为当线程数增长时lock可能变得昂贵。 ConcurrentDictionary<>并不需要锁来访问其项目或修改集合,但如果您使用new,则可能必须使用lock来访问其实例(请考虑使用{{} 1}}用于持有其引用的字段。)