C# 如何确保跨线程的数据同步;“安全”;区域(例如,不在关键区域)未锁定所有内容
我们正在使用一个专有的API,它需要在某个点上同步数据。 我曾考虑过一些确保数据一致性的方法,但我渴望获得更多关于更好解决方案的信息 下面是一个长时间运行的C# 如何确保跨线程的数据同步;“安全”;区域(例如,不在关键区域)未锁定所有内容,c#,multithreading,concurrency,synchronization,task,C#,Multithreading,Concurrency,Synchronization,Task,我们正在使用一个专有的API,它需要在某个点上同步数据。 我曾考虑过一些确保数据一致性的方法,但我渴望获得更多关于更好解决方案的信息 下面是一个长时间运行的任务概述了API同步 new Task(() => { while(true) { // Here other threads can access any API object (that's fine) API.CriticalOperationStart(); // Between
任务
概述了API同步
new Task(() =>
{
while(true)
{
// Here other threads can access any API object (that's fine)
API.CriticalOperationStart(); // Between start and end no API Object may be used
API.CriticalOperationEnd();
// Here other threads can access any API object (that's fine too)
}
}, TaskCreationOptions.LongRunning).Start();
这是一个单独的任务,实际执行一些数据同步
起点和终点之间的区域至关重要否当API处于此关键步骤时,可以执行其他API调用
下面是一些使用不同API对象的非保护线程:
// multiple calls to different API objects should not be exclusive
OtherThread1
APIObject1.SetData(42);
OtherThread2
APIObject2.SetData(43);
约束条件:
在API处于关键部分期间,不允许调用任何APIObject
方法。
两个SetData
调用都允许同时执行。它们彼此不干涉,只干涉临界截面。
一般来说,从多个线程访问一个APIObject
不是线程安全的,但是访问多个APIObjects
不会干扰API,除非在关键部分。
在使用任何APIObject
方法时,决不能执行临界部分。
不需要从多个线程保护对一个APIObject
的访问
琐碎的方法 使用一个lock对象和
lock
关键部分以及对API对象的每次调用
这将有效地工作,但会创建许多不必要的锁,因为一次只能访问一个APIObject
并发操作容器 使用并发容器的单个实例,其中,
APIObject
的每次修改都被放入线程安全容器中,并通过遍历关键部分之外的容器并调用所有操作,在上面的任务中显式执行。(不是使用者模式,因为等待容器的新条目不能阻止任务,因为关键部分必须定期执行)
这带来了一些缺点。捕获上下文时的关闭问题可能是其中之一。另一种方法是从APIObject
读取数据,只要容器中的操作没有执行,就会返回旧数据。更糟糕的是,如果将APIObject
的创建放在容器中,随后的代码假设已经创建了它
用等待句柄和原子增量来弥补 可以使用
ManualResetEvent
保护每个APIObject
访问。关键部分将等待信号由APIObjects
设置,仅当对APIObjects
的所有调用完成时(访问APIObjects
时的某种原子增量/减量)才会设置信号
听起来是解决死锁的好方法。当连续APIObject
调用阻止信号设置时,可能会长时间锁定关键部分
无法解决在关键段期间可能无法访问APIObjects
的问题,因为此构造仅在另一个方向进行防护。
需要额外的锁定(例如,在关键部分的Monitor.IsEntered
,以不锁定对不同APIObjects的同时调用)
=>糟糕的方法是,如果复制一个APIObject
相对便宜(或者如果它相当昂贵并且你不经常同步),那么你可以把对象放在一个包含单个全局时间戳和局部时间戳的包装器中。更新对象时,首先检查是否global\u timestamp==long.MaxValue
:如果为true,则返回一个破坏性更新的对象;如果global\u时间戳!=long.MaxValue
和global\u timestamp==local\u timestamp
,然后返回一个破坏性更新的对象。但是如果global\u时间戳!=long.MaxValue
和全局时间戳!=然后返回对象的更新副本,并设置local\u timestamp=global\u timestamp
。执行同步时,请使用联锁
更新来设置global\u timestamp=DateTime.UtcNow.ToBinary
,同步完成时设置global\u timestamp=long.MaxValue
。这样,在执行同步时,程序的其余部分就不必暂停,并且同步应该具有一致的数据
// APIObject provided to you
public class APIObject {
private string foo;
public void setFoo(string _foo) {
this.foo = _foo;
}
}
// Global Timestamp, readonly version for wrappers and readwrite version for sync
public class GlobalTimestamp {
protected long timestamp = long.MaxValue;
public long getTimestamp() {
return timestamp;
}
}
public class GlobalTimestampRW extends GlobalTimestamp {
public void startSync(long _timestamp) {
long value = System.Threading.Interlocked.CompareExchange(ref timestamp, _timestamp, long.MaxValue);
if(value != long.MaxValue) throw exception; // somebody else called this method already
}
public void endSync(long _timestamp) {
long value = System.Threading.Interlocked.CompareExchange(ref timestamp, long.MaxValue, _timestamp);
if(value != _timestamp) throw exception; // somebody else called this method already
}
}
// Wrapper
public class APIWrapper {
private APIObject apiObject;
private GlobalTimestamp globalTimestamp;
private long localTimestamp = long.MinValue;
public APIObject setFoo(string _foo) {
long tempGlobalTimestamp = globalTimestamp.getTimestamp();
if(tempGlobalTimestamp == long.MaxValue || tempGlobalTimestamp == localTimestamp) {
apiObject.setFoo(_foo);
return apiObject;
} else {
apiObject = apiObject.copy();
apiObject.setFoo(_foo);
localTimestamp = tempGlobalTimestamp;
return apiObject;
}
}
}
GlobalTimestampRW globalTimestamp;
new Task(() =>
{
while(true)
{
long timestamp = DateTime.UtcNow.ToBinary();
globalTimestamp.startSync(timestamp);
API.CriticalOperationStart(); // Between start and end no API Object may be used
API.CriticalOperationEnd();
globalTimestamp.endSync(timestamp);
}
}, TaskCreationOptions.LongRunning).Start();
如果复制一个APIObject
相对便宜(或者如果它相当昂贵,并且您不经常同步),那么您可以将对象放入一个包装器中,该包装器包含一个单例global\u时间戳
和一个local\u时间戳
。更新对象时,首先检查是否global\u timestamp==long.MaxValue
:如果为true,则返回一个破坏性更新的对象;如果global\u时间戳!=long.MaxValue
和global\u timestamp==local\u timestamp
,然后返回一个破坏性更新的对象。但是如果global\u时间戳!=long.MaxValue
和全局时间戳!=然后返回对象的更新副本,并设置local\u timestamp=global\u timestamp
。执行同步时,请使用联锁
更新来设置global\u timestamp=DateTime.UtcNow.ToBinary
,同步完成时设置global\u timestamp=long.MaxValue
。这样,在执行同步时,程序的其余部分就不必暂停,并且同步应该具有一致的数据
// APIObject provided to you
public class APIObject {
private string foo;
public void setFoo(string _foo) {
this.foo = _foo;
}
}
// Global Timestamp, readonly version for wrappers and readwrite version for sync
public class GlobalTimestamp {
protected long timestamp = long.MaxValue;
public long getTimestamp() {
return timestamp;
}
}
public class GlobalTimestampRW extends GlobalTimestamp {
public void startSync(long _timestamp) {
long value = System.Threading.Interlocked.CompareExchange(ref timestamp, _timestamp, long.MaxValue);
if(value != long.MaxValue) throw exception; // somebody else called this method already
}
public void endSync(long _timestamp) {
long value = System.Threading.Interlocked.CompareExchange(ref timestamp, long.MaxValue, _timestamp);
if(value != _timestamp) throw exception; // somebody else called this method already
}
}
// Wrapper
public class APIWrapper {
private APIObject apiObject;
private GlobalTimestamp globalTimestamp;
private long localTimestamp = long.MinValue;
public APIObject setFoo(string _foo) {
long tempGlobalTimestamp = globalTimestamp.getTimestamp();
if(tempGlobalTimestamp == long.MaxValue || tempGlobalTimestamp == localTimestamp) {
apiObject.setFoo(_foo);
return apiObject;
} else {
apiObject = apiObject.copy();
apiObject.setFoo(_foo);
localTimestamp = tempGlobalTimestamp;
return apiObject;
}
}
}
GlobalTimestampRW globalTimestamp;
new Task(() =>
{
while(true)
{
long timestamp = DateTime.UtcNow.ToBinary();
globalTimestamp.startSync(timestamp);
API.CriticalOperationStart(); // Between start and end no API Object may be used
API.CriticalOperationEnd();
globalTimestamp.endSync(timestamp);
}
}, TaskCreationOptions.LongRunning).Start();
我突然想到,您可能会为此滥用读写器锁。每个线程都会请求一个读锁,而这个读锁不会锁定其他同样需要读锁的线程