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

发布于 2024-08-24 15:09:02 字数 1969 浏览 1 评论 0原文

我有多个线程共享信号量的使用。线程 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 解决方案的一种变体。我主要希望有更优雅的解决方案可用,但我怀疑就像这个项目的其他所有内容一样,这都是蛮力和极端情况:-(。

I have multiple threads that share use of a semaphore. Thread A holds the semaphore (using lock) and threads B and C are waiting on that same semaphore (also using lock). The threads share global variables, etc.

Is there a technique in C# that I can use to shut down thread B? I can set a flag in A and have thread B check that flag and exit as soon as it gets control of the semaphore, but I don't know of any technique to allow thread A to yield the semaphore to thread B (and get it back when thread B exits) without the risk of thread C seizing control.

Anyone have any suggestions how to address this design problem? I can rewrite the program as necessary if I am approaching this incorrectly.

[Edit]
A commenter has pointed out that I am using the wrong terminology. The commenter is correct - I am using a critical section, but given that everything is running in a single process, in this example critical sections are functionally equivalent to the more general term 'semaphore'.

[Edit]
Someone asked for more details, so here it is.

There can be multiple threads executing Code A. There's only ever one thread executing Code B.

Code 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)

Code 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

I suspect that a variant of Aaron's solution will be what I use. I was mostly hoping there was somewhat more elegant solution was available, but I suspect that like everything else about this project, it's all brute force and corner cases :-(.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

脱离于你 2024-08-31 15:09:02

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

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

显而易见的解决方案是使用多个锁和/或同步原语。您可以将lock 的语义与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 发出事件信号。

I'm fairly certain that there's no way to yield control to a specific thread, which seems to be what you're trying to do. You can only yield, period - it's up to the Windows scheduler to decide what thread gets to run next.

The situation is that you have three threads, A, B, and C. A has the lock, B and C are waiting for it, and you want a way to guarantee that B gets to executed next.

The obvious solution is to use more than one lock and/or sync primitive. You can combine the semantics of lock with a ManualResetEvent. Make thread C wait for both the event and the critical section, but thread B only has to wait for the critical section. Under normal circumstances, you signal the event just before releasing the lock, which leaves it up to the OS to decide which thread to execute. In the special case, you don't signal the event at all, leaving thread B to execute while C is still blocked.

Once B is done, then you signal the event to let C finish.


An (untested) example would be:

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

This essentially puts Thread A in charge of when Thread C gets to execute. Threads A and B will compete normally but it's up to Thread A to signal the event for Thread C.

十秒萌定你 2024-08-31 15:09:02

看一下如何使用 Monitor 和 Monitor.Pulse 这不会让你完全得到你想要的特定线程的结果,但它允许你在线程和关键部分之间转移控制。

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

最后,在我看来,您的场景是使用 .NET 事件 相反。

Take a look at using Monitor and Monitor.Pulse this will not get you exactly what you want in yielding to a specific thread but it will allow you to transfer control between threads and critical sections.

It's not clear to me what problem you are trying to solve. You may also be able to solve your problem using ReaderWriterLockSlim as well.

Lastly your scenerio sounds to me like an appropriate place to use .NET events instead.

口干舌燥 2024-08-31 15:09:02

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

编辑了额外的免责声明: 如果您想扩展这一概念,请非常非常警惕死锁。

在某些情况下,无论 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;
            }
        }
    }

如果 额外的代码,但没有人声称并发是容易的。 :)

(Disclaimer: If your only use case is that specific one, @Aaronaught's solution of simply using ManualResetEvents is probably the easiest.)

Edited for additional disclaimer: If you want to extend this concept, be very very wary of deadlocks.

If there are situations where you might want C to do work regardless of whether B has done stuff, but always want B to go first if C is waiting, here's one simple solution:

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

A bunch of extra code, but no one's ever claimed concurrency is easy. :)

我做我的改变 2024-08-31 15:09:02

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

Aaronaught's solution looks sound, but I think a simpler one would be to use a litte more shared state and just a single lock.

Basically you pass control between the threads and the threads decide if it's time for them to work or not. If it's not, they simple PulseAll (move all existing waiting threads into the ready queue) and Wait to (be pulsed and then) get the lock again. At some point, it's decided when ThreadC is good to go.

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
    }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文