我想知道,假设我将它作为类实例级变量:
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);
}
从线程安全的角度来看是否存在任何问题?
答案 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
实际上,您通常会将这些字段包装在单个不可变类中,并将其作为单个属性而不是两个相互依赖的属性公开,以简化代码并使其更加健壮。
另请注意,如果您要对字典本身进行更改(例如添加或删除项目),那么您应该锁定对字典的所有读写访问权限(使用相同的锁定对象)。
答案 1 :(得分:1)
在你的情况下有两个对象:集合实例和项目实例。
您正在同步对集合的访问权限。只有在修改集合(添加,删除,清除,data
)时,才能在访问new
的任何地方执行此操作。如果收集没有变化 - 您不必同步访问它。
项目实例也可能需要同步(如果多个线程可以获取并使用它),遵循与之前完全相同的逻辑。
与项目属性等相同。始终尝试考虑谁将访问它以及如何准确。开始使用并发集合可能是值得的,因为当线程数增长时lock
可能变得昂贵。 ConcurrentDictionary<>
并不需要锁来访问其项目或修改集合,但如果您使用new
,则可能必须使用lock
来访问其实例(请考虑使用{{} 1}}用于持有其引用的字段。)