代码之家  ›  专栏  ›  技术社区  ›  Tom West

.NET并发性问题:我可以向另一个线程生成信号量吗?

  •  3
  • Tom West  · 技术社区  · 14 年前

    我有多个线程共享信号量的使用。线程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
    

    我怀疑我用的是亚伦溶液的变体。我主要是希望有一些更优雅的解决方案,但我怀疑,像这个项目的其他所有内容一样,这都是蛮力和角落案件。

    4 回复  |  直到 14 年前
        1
  •  4
  •   Aaronaught    14 年前

    我相当肯定,没有办法对 具体的 线程,这似乎是你要做的。您只能让步,句点-由Windows调度程序决定下一个运行哪个线程。

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

    显而易见的解决方案是使用多个锁和/或同步原语。您可以将 lock 用一个 ManualResetEvent . 使线程C等待两个事件 关键部分,但线程B只需等待关键部分。在正常情况下,在释放锁之前就向事件发出信号,这样就由操作系统决定要执行哪个线程。在特殊情况下,您根本不向事件发出信号,在C仍然被阻塞时让线程B执行。

    一旦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发出事件信号。

        2
  •  1
  •   Firestrand    14 年前

    看一下使用监视器和 Monitor.Pulse 这并不能满足您向特定线程屈服时的具体需求,但它将允许您在线程和关键部分之间传输控制权。

    我不清楚你想解决什么问题。您还可以使用 ReaderWriterLockSlim 也。

    最后,我觉得你的场景是一个适合使用.NET的地方。 events 相反。

        3
  •  1
  •   Tanzelax    14 年前

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

    为附加免责声明编辑: 如果您想扩展这个概念,就要非常小心死锁。

    如果在某些情况下,不管B是否做过什么,你都可能希望C做工作,但如果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;
                }
            }
        }
    

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

        4
  •  1
  •   G-Wiz RameshVel    14 年前

    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
        }
      }
    }