是否存在保证C#中FIFO顺序的同步类?

时间:2009-06-07 13:15:20

标签: c# synchronization

它是什么以及如何使用?

我需要它,因为我有一个每秒插入DB的计时器,我在计时器处理程序和主线程之间有一个共享资源。 我想保证,如果计时器处理程序在插入中花费超过一秒钟,则应按顺序执行等待的线程。 这是我的计时器处理程序的示例代码

private void InsertBasicVaraibles(object param)
{
            try
            {
                DataTablesMutex.WaitOne();//mutex for my shared resources
                //insert into DB
            }
            catch (Exception ex)
            {
                //Handle
            }
            finally
            {
                DataTablesMutex.ReleaseMutex();
            }
}

但目前互斥锁并不保证任何订单。 我提出详细问题后没有答案!!!

8 个答案:

答案 0 :(得分:29)

你需要编写自己的类才能做到这一点,我找到了这个例子(粘贴因为它看起来好像网站的域名已经失效):

using System.Threading;

public sealed class QueuedLock
{
    private object innerLock;
    private volatile int ticketsCount = 0;
    private volatile int ticketToRide = 1;

    public QueuedLock()
    {
        innerLock = new Object();
    }

    public void Enter()
    {
        int myTicket = Interlocked.Increment(ref ticketsCount);
        Monitor.Enter(innerLock);
        while (true)
        {

            if (myTicket == ticketToRide)
            {
                return;
            }
            else
            {
                Monitor.Wait(innerLock);
            }
        }
    }

    public void Exit()
    {
        Interlocked.Increment(ref ticketToRide);
        Monitor.PulseAll(innerLock);
        Monitor.Exit(innerLock);
    }
}

使用示例:

QueuedLock queuedLock = new QueuedLock();

try
{
   queuedLock.Enter();
   // here code which needs to be synchronized
   // in correct order
}
finally
{
    queuedLock.Exit();
}

Source via Google cache

答案 1 :(得分:11)

只是阅读Joe Duffy的“Windows上的并发编程”,听起来你通常从.NET监视器获取FIFO行为,但在某些情况下不会发生这种情况。

本书的第273页说:“因为监视器在内部使用内核对象,它们表现出与OS同步机制也表现出的相同的大致FIFO行为(在前一章中描述)。监视器是不公平的,所以如果另一个线程偷偷摸摸在唤醒等待线程尝试获取锁之前获取锁,允许偷偷摸摸的线程获取锁。“

我无法立即找到“上一章”中引用的部分,但 注意到在最新版本的Windows中故意不公平地锁定以提高可伸缩性并减少锁定车队

你肯定需要锁定FIFO吗?也许有一种不同的方式来解决这个问题。我不知道.NET中哪些锁保证是FIFO。

答案 2 :(得分:7)

您应该重新设计系统,使其不依赖于线程的执行顺序。例如,让你的线程进行可能需要超过一秒的数据库调用,让你的线程将它们执行的命令放入一个数据结构,如队列(如果有一些东西说“这应该是在另一个之前“)。然后,在业余时间,排空队列并按正确的顺序一次插入一个db。

答案 3 :(得分:1)

任何内置同步对象都没有保证顺序:http://msdn.microsoft.com/en-us/library/ms684266(VS.85).aspx

如果你想要一个有保证的订单,你必须自己尝试构建一些东西,注意它并不像听起来那么容易,特别是当多个线程同时(接近)到达同步点时。在某种程度上,它们将被释放的顺序将永远是“随机的”,因为你无法预测到达点的顺序,它真的重要吗?

答案 4 :(得分:1)

实际上答案很好,但我通过删除计时器并将方法(先前的计时器处理程序)运行到后台线程中解决了问题,如下所示

    private void InsertBasicVaraibles()
    {
         int functionStopwatch = 0;
         while(true)
         {

           try
           {
             functionStopwatch = Environment.TickCount;
             DataTablesMutex.WaitOne();//mutex for my shared resources
             //insert into DB 
           }
           catch (Exception ex)
           {
             //Handle            
           }
           finally            
           {                
              DataTablesMutex.ReleaseMutex();
           }

           //simulate the timer tick value
           functionStopwatch = Environment.TickCount - functionStopwatch;
           int diff = INSERTION_PERIOD - functionStopwatch;
           int sleep = diff >= 0 ?  diff:0;
           Thread.Sleep(sleep);
        }
    }

答案 5 :(得分:1)

跟进Matthew Brindley的回答。

如果从

转换代码
lock (LocalConnection.locker) {...}

然后你可以做一个IDisposable或做我做的事情:

public static void Locking(Action action) {
  Lock();
  try {
    action();
  } finally {
    Unlock();
  }
}

LocalConnection.Locking( () => {...});

我决定反对IDisposable,因为它会在每次调用时创建一个新的不可见对象。

关于重入问题,我将代码修改为:

public sealed class QueuedLock {
    private object innerLock = new object();
    private volatile int ticketsCount = 0;
    private volatile int ticketToRide = 1;
    ThreadLocal<int> reenter = new ThreadLocal<int>();

    public void Enter() {
        reenter.Value++;
        if ( reenter.Value > 1 ) 
            return;
        int myTicket = Interlocked.Increment( ref ticketsCount );
        Monitor.Enter( innerLock );
        while ( true ) {
            if ( myTicket == ticketToRide ) {
                return;
            } else {
                Monitor.Wait( innerLock );
            }
        }
    }

    public void Exit() {
        if ( reenter.Value > 0 ) 
            reenter.Value--;
        if ( reenter.Value > 0 ) 
            return;
        Interlocked.Increment( ref ticketToRide );
        Monitor.PulseAll( innerLock );
        Monitor.Exit( innerLock );
    }
}

答案 6 :(得分:1)

万一有人需要F#中的Matt解决方案

    type internal QueuedLock() =
        let innerLock = Object()
        let ticketsCount = ref 0
        let ticketToRide = ref 1
        member __.Enter () =
            let myTicket = Interlocked.Increment ticketsCount
            Monitor.Enter innerLock
            while myTicket <> Volatile.Read ticketToRide do
                Monitor.Wait innerLock |> ignore
        member __.Exit () =
            Interlocked.Increment ticketToRide |> ignore
            Monitor.PulseAll innerLock
            Monitor.Exit innerLock

答案 7 :(得分:0)

注意:提供的示例容易出现死锁。 示例:

QueuedLock queuedLock = new QueuedLock();
void func1()
{
    try
    {
       queuedLock.Enter();
       fubc2()
    }
    finally
    {
        queuedLock.Exit();
    }
}

void func2()
{
    try
    {
       queuedLock.Enter();  //<<<< DEADLOCK

    }
    finally
    {
        queuedLock.Exit();
    }
}

再。可选解决方案(包括可选的 IDisposable 用法)

public sealed class QueuedLock
{
    private class SyncObject : IDisposable
    {
        private Action m_action = null;
        public SyncObject(Action action)
        {
            m_action = action;
        }

        public void Dispose()
        {
            lock (this)
            {
                var action = m_action;
                m_action = null;
                action?.Invoke();
            }
        }
    }

    private readonly object m_innerLock = new Object();
    private volatile uint m_ticketsCount = 0;
    private volatile uint m_ticketToRide = 1;

    public bool Enter()
    {
        if (Monitor.IsEntered(m_innerLock))
            return false;

        uint myTicket = Interlocked.Increment(ref m_ticketsCount);
        Monitor.Enter(m_innerLock);
        while (true)
        {
            if (myTicket == m_ticketToRide)
                return true;

            Monitor.Wait(m_innerLock);
        }
    }

    public void Exit()
    {
        Interlocked.Increment(ref m_ticketToRide);
        Monitor.PulseAll(m_innerLock);
        Monitor.Exit(m_innerLock);
    }

    public IDisposable GetLock()
    {
        if (Enter())
            return new SyncObject(Exit);

        return new SyncObject(null);
    }
}

用法

QueuedLock queuedLock = new QueuedLock();
void func1()
{
    bool isLockAquire = false;
    try
    {
       isLockAquire = queuedLock.Enter();
       // here code which needs to be synchronized in correct order
    }
    finally
    {
        if (isLockAquire)
           queuedLock.Exit();
    }
}

QueuedLock queuedLock = new QueuedLock();
void func1()
{
    using (queuedLock.GetLock())
    {
       // here code which needs to be synchronized in correct order
    }
}