3

我有多个线程共享信号量的使用。线程 A 持有信号量(使用锁),线程 B 和 C 正在等待同一个信号量(也使用锁)。线程共享全局变量等。

C# 中是否有可以用来关闭线程 B 的技术?我可以在 A 中设置一个标志,让线程 B 检查该标志并在它控制信号量后立即退出,但我不知道有任何技术可以让线程 A 将信号量交给线程 B(并得到它当线程 B 退出时返回)没有线程 C 夺取控制权的风险。

有人对如何解决这个设计问题有任何建议吗?如果我不正确地处理这个问题,我可以根据需要重写程序。

[编辑] 一位评论者指出我使用了错误的术语。评论者是正确的 - 我正在使用一个关键部分,但鉴于一切都在一个进程中运行,在这个例子中,关键部分在功能上等同于更通用的术语“信号量”。

[编辑] 有人询问更多细节,所以在这里。

可以有多个线程执行代码 A。只有一个线程执行代码 B。

代码 A:

private static Thread workerThread = null;

lock (lockObject)
{
    ... do some work ...

    if (...condition...)
    {
        if (workerThread != null)
        {
            // Kill the worker thread and continue only after it is dead.
            quitWorkerThread = true;
            // Wait for the thread to die.
            while (workerThread.IsAlive)
            {
                Thread.Sleep(50);
            }
            workerThread = null;
            quitWorkerThread = false;
        } // if (workerThread != null)
    } // if (...condition...)

    ... do some more work ...

    if (...condition...)
    {
        if (workerThread == null)
        {
            // Start the worker thread.
            workerThread = new Thread(WorkerThread);
            workerThread.Start();
        } // if (workerThread == null)
    } // if (...condition...)

    ... do even more work ...

} // lock (lockObject)

代码 B:

private void WorkerThread()
{
    while (true)
    {
        if (quitWorkerThread)
        {
            return;
        }

        Thread.Sleep (2000);

        if (quitWorkerThread)
        {
            return;
        }

        lock(lockObject)
        {
            if (quitWorkerThread)
            {
                return;
            }
            ... do some work ...
        } // lock(lockObject)
    } // while (true)
} // WorkerThread

我怀疑我将使用 Aaron 解决方案的变体。我主要希望有一些更优雅的解决方案可用,但我怀疑就像这个项目的其他一切一样,这都是蛮力和极端情况:-(。

4

4 回答 4

4

我相当确定没有办法让控制权交给特定线程,这似乎是你想要做的。您只能让步 - 由 Windows 调度程序决定接下来运行哪个线程。

情况是您有三个线程,A、B 和 C。A 有锁,B 和 C 正在等待它,并且您想要一种方法来保证 B 下一个执行。

显而易见的解决方案是使用多个锁和/或同步原语。您可以将 的语义lock与 a结合起来ManualResetEvent。让线程 C 等待事件临界区,而线程 B 只需要等待临界区。在正常情况下,您在释放锁之前发出事件信号,这让操作系统决定执行哪个线程。在特殊情况下,您根本不发出事件信号,让线程 B 执行,而 C 仍被阻塞。

一旦 B 完成,然后您发出事件信号让 C 完成。


一个(未经测试的)示例是:

// Main thread is Thread A
object myLock = new Object();
AutoResetEvent myEvent = new AutoResetEvent(false);
ManualResetEvent completedEvent = new ManualResetEvent(false);

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        lock (myLock)
        {
            // Do some work
        }
    }
    completedEvent.Set();
});  // Thread B

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        myEvent.WaitOne();
        lock (myLock)
        {
            // Do some work
        }
    }
});  // Thread C

// Main loop for thread A
while (true)
{
    lock (myLock)
    {
        // Do some work
        if (SomeSpecialCondition)
            break;
        else
            myEvent.Set();
    }
}

completedEvent.WaitOne(); // Wait for B to finish processing
if (SomeSpecialCondition) // If we terminated without signaling C...
    myEvent.Set();        // Now allow thread C to clean up

这实质上是让线程 A 负责线程 C 何时开始执行。线程 A 和 B 将正常竞争,但由线程 A 为线程 C 发出事件信号。

于 2010-03-06T03:11:44.663 回答
1

看看使用 Monitor 和Monitor.Pulse ,这不会让您在屈服于特定线程时获得您想要的确切信息,但它可以让您在线程和关键部分之间转移控制。

我不清楚您要解决什么问题。您也可以使用ReaderWriterLockSlim解决您的问题。

最后,在我看来,您的场景听起来像是使用 .NET事件的合适场所。

于 2010-03-06T03:32:48.947 回答
1

(免责声明:如果您唯一的用例是那个特定的用例,@Aaronaught 简单地使用 ManualResetEvents 的解决方案可能是最简单的。)

编辑附加免责声明: 如果您想扩展此概念,请非常小心死锁。

如果在某些情况下您可能希望 C 工作而不管 B 是否已完成工作,但如果 C 正在等待,则始终希望 B 先走,这是一个简单的解决方案:

    object lockObject = new object();
    int WaitCounter = 0;

    void B()
    {
        System.Threading.Interlocked.Increment(ref WaitCounter);

        try
        {
            lock (lockObject)
            {
            }
        }
        finally
        {
            System.Threading.Interlocked.Decrement(ref WaitCounter);
        }
    }

    void C()
    {
        while (true)
        {
            // always attempt to yield to other threads first
            System.Threading.Thread.Sleep(0);
            lock (lockObject)
            {
                if (WaitCounter > 0)
                    continue;

                // ...

                return;
            }
        }
    }

一堆额外的代码,但从来没有人声称并发是容易的。:)

于 2010-03-06T03:37:15.463 回答
1

Aaronaught 的解决方案看起来不错,但我认为更简单的解决方案是使用更共享的状态和单个锁。

基本上,您在线程之间传递控制权,线程决定它们是否该工作了。如果不是,他们简单的 PulseAll(将所有现有的等待线程移动到就绪队列中)并等待(被脉冲然后)再次获得锁。在某些时候,它决定了何时可以使用 ThreadC。

public class ThreadsGettinCrazy {
  static readonly _sync = new object();
  static bool _threadCReady = false;

  public void ThreadA() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadB() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadC() {
    lock(_sync) {
      while (!_threadCReady) {
        Monitor.PulseAll(_sync);
        Monitor.Wait(_sync);
      }
      // do work
    }
  }
}
于 2010-03-06T07:08:23.120 回答