线程安全递减计数器
Thread Safe decrement counter
问题
在一个项目案例中,我需要创建多个线程,这些线程从队列中挑选任务并 运行 处理它们。如果一组其他任务仍在 运行ning,则其中一些任务无法 运行。考虑像文件复制和碎片整理 (运行s when system is idle) in windows .
解决方案
为了实现这个,我创建了一个 class 基于
System.Threading.CountdownEvent.
每当一个线程从队列中选择一个阻塞任务时,他们将 Increment
CounterEvent
并且在他们完成他们的工作后他们将 Decrement
CounterEvent
。
如果一个线程选择一个低优先级任务,它会Wait
直到CounterEvent
为零然后开始运行ning。
低优先级任务可以立即从 Reset
开始,共 CounterEvent
主线程或并行线程可以通过查询CurrentCount
.
来监控锁的状态
代码如下:
using System;
using System.Diagnostics.Contracts;
using System.Threading;
public class CounterEvent : IDisposable {
private volatile int m_currentCount;
private volatile bool m_disposed;
private ManualResetEventSlim m_event;
// Gets the number of remaining signals required to set the event.
public int CurrentCount {
get {
return m_currentCount;
}
}
// Allocate a thin event, Create a latch in signaled state.
public CounterEvent() {
m_currentCount = 0;
m_event = new ManualResetEventSlim();
m_event.Set(); //
}
// Decrements the counter. if counter is zero signals other threads to continue
public void Decrement() {
ThrowIfDisposed();
Contract.Assert(m_event != null);
int newCount = 0;
if (m_currentCount >= 0) {
#pragma warning disable 0420
newCount = Interlocked.Decrement(ref m_currentCount);
#pragma warning restore 0420
}
if (newCount == 0) {
m_event.Set();
}
}
// increments the current count by one.
public void Increment() {
ThrowIfDisposed();
#pragma warning disable 0420
Interlocked.Increment(ref m_currentCount);
#pragma warning restore 0420
}
// Resets the CurrentCount to the value of InitialCount.
public void Reset() {
ThrowIfDisposed();
m_currentCount = 0;
m_event.Set();
}
// Blocks the current thread until the System.Threading.CounterEvent is set.
public void Wait() {
ThrowIfDisposed();
m_event.Wait();
}
/// <summary>
/// Throws an exception if the latch has been disposed.
/// </summary>
private void ThrowIfDisposed() {
if (m_disposed) {
throw new ObjectDisposedException("CounterEvent");
}
}
// According to MSDN this is not thread safe
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
// According to MSDN Dispose() is not thread-safe.
protected virtual void Dispose(bool disposing) {
if (disposing) {
m_event.Dispose();
m_disposed = true;
}
}
}
问题
这段代码能按预期工作吗?
有什么我没有看到的缺陷吗?
这样做有没有更好的选择?
备注
应用程序是用 System.Threading.Thread 编写的,对我来说转换它的成本非常高,但是一个很好的替代解决方案总是值得为将来努力。
这应该是一个原子操作,如果你这样做,它不是线程安全的
if (m_currentCount >= 0)
{
newCount = Interlocked.Decrement(ref m_currentCount);
}
可能 m_currentCount
在 if
和 Interlocked.Decrement
之间改变
你应该重写你的逻辑来使用 Interlocked.CompareExchange
我也会在你分配给 m_currentCount
的每个地方使用 Interlocked.Exchange
然后你不需要 volatile
和 pragma
您还应该注意,在非常重的负载下,可能会发生重置事件 Set
丢失
问题
在一个项目案例中,我需要创建多个线程,这些线程从队列中挑选任务并 运行 处理它们。如果一组其他任务仍在 运行ning,则其中一些任务无法 运行。考虑像文件复制和碎片整理 (运行s when system is idle) in windows .
解决方案
为了实现这个,我创建了一个 class 基于 System.Threading.CountdownEvent.
每当一个线程从队列中选择一个阻塞任务时,他们将 Increment
CounterEvent
并且在他们完成他们的工作后他们将 Decrement
CounterEvent
。
如果一个线程选择一个低优先级任务,它会Wait
直到CounterEvent
为零然后开始运行ning。
低优先级任务可以立即从 Reset
开始,共 CounterEvent
主线程或并行线程可以通过查询CurrentCount
.
代码如下:
using System;
using System.Diagnostics.Contracts;
using System.Threading;
public class CounterEvent : IDisposable {
private volatile int m_currentCount;
private volatile bool m_disposed;
private ManualResetEventSlim m_event;
// Gets the number of remaining signals required to set the event.
public int CurrentCount {
get {
return m_currentCount;
}
}
// Allocate a thin event, Create a latch in signaled state.
public CounterEvent() {
m_currentCount = 0;
m_event = new ManualResetEventSlim();
m_event.Set(); //
}
// Decrements the counter. if counter is zero signals other threads to continue
public void Decrement() {
ThrowIfDisposed();
Contract.Assert(m_event != null);
int newCount = 0;
if (m_currentCount >= 0) {
#pragma warning disable 0420
newCount = Interlocked.Decrement(ref m_currentCount);
#pragma warning restore 0420
}
if (newCount == 0) {
m_event.Set();
}
}
// increments the current count by one.
public void Increment() {
ThrowIfDisposed();
#pragma warning disable 0420
Interlocked.Increment(ref m_currentCount);
#pragma warning restore 0420
}
// Resets the CurrentCount to the value of InitialCount.
public void Reset() {
ThrowIfDisposed();
m_currentCount = 0;
m_event.Set();
}
// Blocks the current thread until the System.Threading.CounterEvent is set.
public void Wait() {
ThrowIfDisposed();
m_event.Wait();
}
/// <summary>
/// Throws an exception if the latch has been disposed.
/// </summary>
private void ThrowIfDisposed() {
if (m_disposed) {
throw new ObjectDisposedException("CounterEvent");
}
}
// According to MSDN this is not thread safe
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
// According to MSDN Dispose() is not thread-safe.
protected virtual void Dispose(bool disposing) {
if (disposing) {
m_event.Dispose();
m_disposed = true;
}
}
}
问题
这段代码能按预期工作吗? 有什么我没有看到的缺陷吗? 这样做有没有更好的选择?
备注
应用程序是用 System.Threading.Thread 编写的,对我来说转换它的成本非常高,但是一个很好的替代解决方案总是值得为将来努力。
这应该是一个原子操作,如果你这样做,它不是线程安全的
if (m_currentCount >= 0)
{
newCount = Interlocked.Decrement(ref m_currentCount);
}
可能 m_currentCount
在 if
和 Interlocked.Decrement
之间改变
你应该重写你的逻辑来使用 Interlocked.CompareExchange
我也会在你分配给 m_currentCount
的每个地方使用 Interlocked.Exchange
然后你不需要 volatile
和 pragma
您还应该注意,在非常重的负载下,可能会发生重置事件 Set
丢失