【问题标题】:How to create a FIFO/strong semaphore如何创建先进先出/强信号量
【发布时间】:2014-05-01 20:07:14
【问题描述】:

我需要使用我自己的信号量类作为基础,在 C# 中编写我自己的 FIFO/强信号量。我找到了this example,但它不太正确,因为我不应该使用 Monitor.Enter/Exit。

这些是我的常规信号量的方法,我想知道是否有一种简单的方法可以将其调整为 FIFO。

public virtual void Acquire()
{

    lock (this)
    {

        while (uintTokens == 0)
        {

            Monitor.Wait(this);

        }

        uintTokens--;

    }

}

public virtual void Release(uint tokens = 1)
{

    lock (this)
    {

        uintTokens += tokens;
        Monitor.PulseAll(this);

    }

}

【问题讨论】:

  • 与您的问题不太相关,但您可能希望避免使用 lock(this) 代码模式,因为它可能导致死锁。见stackoverflow.com/a/251668/1556108
  • 您必须使用信号量吗? ConcurrentQueue<T> 怎么样
  • 你的意思是使用 ConcurrentQueue 作为基础吗?老师希望我们自己制作所有实用程序,因此我们不使用任何内置的并发工具。不过,我有自己的 Channel 课程。感谢LB2的提示,老师没有问题,但无论如何我都会改变它。
  • 查了SemaphoreSlim.WaitAsync的源码,好像已经是FIFO了。为等待创建的每个任务都存储在一个以双链表实现的队列中。
  • FIFO SemaphoreSlim 的基本实现具有作为额外功能的public void Starve(int amount) 方法,可以在here 找到。

标签: c# .net multithreading concurrency


【解决方案1】:

所以SemaphoreSlim 为我们提供了一个很好的起点,因此我们首先将其中一个封装在一个新类中,并将除等待方法之外的所有内容都指向该信号量。

为了获得类似队列的行为,我们需要一个队列对象,并确保它在面对多线程访问时是安全的,我们将使用ConcurrentQueue

在这个队列中,我们将放置TaskCompletionSource 对象。当我们想让某样东西开始等待时,它可以创建一个 TCS,将其添加到队列中,然后通知信号量异步弹出队列中的下一项,并在等待完成时将其标记为“已完成”。我们会知道,队列中的项目数量总是相等或更少的延续。

然后我们就等着 TCS 发来的Task

我们还可以简单地创建一个返回任务的WaitAsync 方法,只需返回它而不是等待它。

public class SemaphoreQueue
{
    private SemaphoreSlim semaphore;
    private ConcurrentQueue<TaskCompletionSource<bool>> queue =
        new ConcurrentQueue<TaskCompletionSource<bool>>();
    public SemaphoreQueue(int initialCount)
    {
        semaphore = new SemaphoreSlim(initialCount);
    }
    public SemaphoreQueue(int initialCount, int maxCount)
    {
        semaphore = new SemaphoreSlim(initialCount, maxCount);
    }
    public void Wait()
    {
        WaitAsync().Wait();
    }
    public Task WaitAsync()
    {
        var tcs = new TaskCompletionSource<bool>();
        queue.Enqueue(tcs);
        semaphore.WaitAsync().ContinueWith(t =>
        {
            TaskCompletionSource<bool> popped;
            if (queue.TryDequeue(out popped))
                popped.SetResult(true);
        });
        return tcs.Task;
    }
    public void Release()
    {
        semaphore.Release();
    }
}

【讨论】:

  • 其中有很多我们没有学过的东西,这意味着我们并不是真的打算使用它们。等等,例如。另外,我有自己的 Channel 类(线程安全队列),我想可以使用它。
  • @FreddieAppsHero 如果您愿意,欢迎您使用自己的工具遵循相同的模型。
  • 我自己的信号量缺少 WaitAsync,如果再扩展它可能会不受欢迎。
  • @FreddieAppsHero 这当然是你的决定。
【解决方案2】:

我创建了一个 FifoSemaphore 类,并且在我的解决方案中成功使用了它。当前的限制是它的行为类似于 Semaphore(1, 1)。

public class FifoSemaphore
{
    private readonly object lockObj = new object();

    private List<Semaphore> WaitingQueue = new List<Semaphore>();


    private Semaphore RequestNewSemaphore()
    {
        lock (lockObj)
        {
            Semaphore newSemaphore = new Semaphore(1, 1);
            newSemaphore.WaitOne();
            return newSemaphore;
        }
    }

    #region Public Functions

    public void Release()
    {
        lock (lockObj)
        {
            WaitingQueue.RemoveAt(0);
            if (WaitingQueue.Count > 0)
            {
                WaitingQueue[0].Release();
            }
        }
    }

    public void WaitOne()
    {
        Semaphore semaphore = RequestNewSemaphore();
        lock (lockObj)
        {
            WaitingQueue.Add(semaphore);
            semaphore.Release();

            if(WaitingQueue.Count > 1)
            {
                semaphore.WaitOne();
            }
        }
        semaphore.WaitOne();

    }

    #endregion
}

用法和普通信号量一样:

FifoSemaphore fifoSemaphore = new FifoSemaphore();

在每个线程上:

fifoSemaphore.WaitOne();
//do work
fifoSemaphore.Release();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多