首先输入线程如何发出信号到其他并发线程相同方法的末尾?
-
11-10-2019 - |
题
首先输入线程如何发出信号到其他并发线程相同方法的末尾?
我有名为say polldpram()的方法。它必须通过网络进行旅行,以获取一些慢速硬件并刷新对象私人数据。如果其他线程同时调用了相同的方法,则不得进行旅行,而是等待第一个即将到来的线程完成工作并简单地退出,因为数据是新鲜的(例如10-30 ms,没有什么不同) 。它易于在方法中易于检测,第二,第三个等线程未首先输入。我使用互锁计数器来检测并发。
问题:我做出了一个不当的选择,可以通过观察计数器(Interlocked.Read)来检测第一个线程的出口,以观察计数器减少的价值降低的价值小于n> 1螺纹入口处检测到的。选择是不好的,因为第一个线程可以在离开后几乎立即重新进入该方法。因此,n> 1个线将永远不会在计数器中检测到浸入。
所以问题:即使第一个线程可以立即再次输入该方法,如何正确检测到第一个输入线程已退出该方法?
谢谢
PS代码
private void pollMotorsData()
{
// Execute single poll with "foreground" handshaking
DateTime start = DateTime.Now;
byte retryCount = 0;
// Pick old data atomically to detect change
uint motorsDataTimeStampPrev = this.MotorsDataTimeStamp;
bool changeDetected = false;
// The design goal of DPRAM is to ease the bottleneck
// Here is a sensor if bottleneck is actually that tight
long parallelThreads = Interlocked.Increment(ref this.motorsPollThreadCount);
try
{
// For first thread entering the counter will be 1
if (parallelThreads <= 1)
{
do
{
// Handshake signal to DPRAM write process on controller side that host PC is reading
this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, true);
try
{
bool canReadMotors = false;
byte[] canReadFrozenDataFlag = new byte[2];
do
{
this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006E_BIT15_FOREGROUND_DONE, canReadFrozenDataFlag);
canReadMotors = (canReadFrozenDataFlag[1] & 0x80) == 0x80;
if (canReadMotors) break;
retryCount++;
Thread.Sleep(1);
} while (retryCount < 10);
if (!canReadMotors)
{
throw new DeltaTauControllerException(this.controller, "Timeout waiting on DPRAM Foreground Handshaking Bit");
}
// The lock is meaningless in contructor as it is certainly single threaded
// but for practice sake the access to data should always be serialized
lock (motorsDataLock)
{
// Obtain fresh content of DPRAM
this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006A_394BYTES_8MOTORS_DATA, this.motorsData);
this.motorsDataBorn = DateTime.Now;
}
}
finally
{
// Handshake signal to DPRAM write process on controller side that host PC has finished reading
this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, false);
}
// Check live change in a separate atom
changeDetected = this.MotorsDataTimeStamp != motorsDataTimeStampPrev;
} while ((!changeDetected) && ((DateTime.Now - start).TotalMilliseconds < 255));
// Assert that result is live
if (!changeDetected)
{
throw new DeltaTauControllerException(this.controller, "DPRAM Background Data timestamp is not updated. DPRAM forground handshaking failed.");
}
}
else
{
// OK. Bottleneck ! The concurrent polls have collided
// Give the controller a breathe by waiting for other thread do the job
// Avoid aggressive polling of stale data, which is not able to be written, locked by reader
// Just wait for other thread do whole polling job and return with no action
// because the data is milliseconds fresh
do
{
// Amount of parallel threads must eventually decrease
// But no thread will leave and decrease the counter until job is done
if (Interlocked.Read(ref this.motorsPollThreadCount) < parallelThreads)
{
// Return is possible because decreased value of concurrentThreads means that
// this very time other thread has finished the poll 1 millisecond ago at most
return;
}
Thread.Sleep(1);
retryCount++;
} while ((DateTime.Now - start).TotalMilliseconds < 255);
throw new DeltaTauControllerException(this.controller, "Timeout 255ms waiting on concurrent thread to complete DPRAM polling");
}
}
finally
{
// Signal to other threads that work is done
Interlocked.Decrement(ref this.motorsPollThreadCount);
// Trace the timing and bottleneck situations
TimeSpan duration = DateTime.Now - start;
if (duration.TotalMilliseconds > 50 || parallelThreads > 1 || retryCount > 0)
{
Trace.WriteLine(string.Format("Controller {0}, DPRAM poll {1:0} ms, threads {2}, retries {3}",
this.controller.number,
duration.TotalMilliseconds,
parallelThreads,
retryCount));
}
}
}
解决方案
您可以做很多不同的方法。如某人已经提到的那样,您可以使用关键部分,但是如果另一个线程阻止,这不会给您“仅退出”的行为。为此,您需要某种标志。您可以使用挥发性的布尔,并锁定该布尔的访问权限,也可以使用单个计数的信号量。最后,您可以使用Mutex。使用同步对象的好处是您可以执行waitforsingleobject并将超时设置为0。然后您可以检查等待是否成功(如果是第一个线程已退出)(在这种情况下,第一个线程为仍在运行)。
其他提示
同步该方法,内部方法检查了最后完成网络访问时间的记录,以确定是否需要再次完成。
您可以使用“锁”关键字支持的C#监视类。
基本上,您的方法可以包裹在锁(lockobj){callmethod()}中
假设所有线程都处于相同的过程中,这将为您提供保护。
如果您需要跨进程锁定,则需要使用MUTEX。
至于您的程序,我将考虑将静态时间戳和缓存的价值放入您的方法中。因此,当方法进入时,如果时间戳在我的可接受范围内,请返回缓存值,否则只需执行获取即可。结合锁定机制,这应该做您需要的事情。
当然,这假定在C#监视器上使用和阻止的时间不会影响您的应用程序的性能。
更新:我已经更新了您的代码,以向您展示我对使用缓存和时间戳的含义。我以为您的“ MotorsData”变量是从电动机投票中返回的东西,因此我没有变量。但是,如果我“被误解了,只需添加一个变量,该变量从代码返回后存储数据。请注意,我没有为您检查任何错误检查,因此您需要处理异常。
static DateTime lastMotorPoll;
const TimeSpan CACHE_PERIOD = new TimeSpan(0, 0, 0, 0, 250);
private object cachedCheckMotorsDataLock = new object();
private void CachedCheckMotorsData()
{
lock (cachedCheckMotorsDataLock) //Could refactor this to perform a try enter which returns quickly if required
{
//If the last time the data was polled is older than the cache period, poll
if (lastMotorPoll.Add(CACHE_PERIOD) < DateTime.Now)
{
pollMotorsData();
lastMotorPoll = DateTime.Now;
}
else //Data is fresh so don't poll
{
return;
}
}
}
private void pollMotorsData()
{
// Execute single poll with "foreground" handshaking
DateTime start = DateTime.Now;
byte retryCount = 0;
// Pick old data atomically to detect change
uint motorsDataTimeStampPrev = this.MotorsDataTimeStamp;
bool changeDetected = false;
try
{
do
{
// Handshake signal to DPRAM write process on controller side that host PC is reading
this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, true);
try
{
bool canReadMotors = false;
byte[] canReadFrozenDataFlag = new byte[2];
do
{
this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006E_BIT15_FOREGROUND_DONE, canReadFrozenDataFlag);
canReadMotors = (canReadFrozenDataFlag[1] & 0x80) == 0x80;
if (canReadMotors) break;
retryCount++;
Thread.Sleep(1);
} while (retryCount < 10);
if (!canReadMotors)
{
throw new DeltaTauControllerException(this.controller, "Timeout waiting on DPRAM Foreground Handshaking Bit");
}
// Obtain fresh content of DPRAM
this.controller.deltaTauTcpClient.Pmac_GetMem(OFFSET_0x006A_394BYTES_8MOTORS_DATA, this.motorsData);
this.motorsDataBorn = DateTime.Now;
}
finally
{
// Handshake signal to DPRAM write process on controller side that host PC has finished reading
this.controller.deltaTauTcpClient.Pmac_SetBit(OFFSET_0x006A_BIT15_FOREGROUND_READ, 15, false);
}
// Check live change in a separate atom
changeDetected = this.MotorsDataTimeStamp != motorsDataTimeStampPrev;
} while ((!changeDetected) && ((DateTime.Now - start).TotalMilliseconds < 255));
// Assert that result is live
if (!changeDetected)
{
throw new DeltaTauControllerException(this.controller, "DPRAM Background Data timestamp is not updated. DPRAM forground handshaking failed.");
}
}
}