【问题标题】:.net construct / pattern to block by segment of code, not by thread.net 构造/模式按代码段而不是线程阻塞
【发布时间】:2011-04-12 15:28:29
【问题描述】:

.net 中是否有一个结构或模式定义了一段代码可以被多个线程访问,但如果任何线程在其他代码段中则阻塞(反之亦然)?例如:

void SomeOperationA()
{
    Block( B ) 
    {   
        Segment1: 
        ... only executes if no threads are executing in Segment2 ... 
    }    
}

}

void SomeOperationB()
{
    Block( A ) 
    { 
        Segment2: 
        ... only executes if no threads are executing in Segment1 ... 
    }     
}

编辑
几个线程应该能够同时访问 Segment1/Segment2(一次只有一个 Segment 处于“活动状态”。如果 Segment 1 正在执行,另一个线程应该能够执行 Segment1 而不是 Segment2。

编辑 2
鉴于所有 cmets/回复和我的真实场景,我意识到要求 Segment2 由多线程访问有点疯狂。

【问题讨论】:

  • 多个线程可以同时执行Segment2吗?
  • 听起来像是饥饿的秘诀。
  • @SFun28:这个在现实世界中的应用是什么?
  • 这是现实世界的场景:Segment2 将 ConcurrentDictionary 序列化到磁盘。不时发生。序列化的时候,一定要保证字典是稳定的。 Segment1 读取和/或更改数据。它是线程安全的,因为 ConcurrentDictionary 是线程安全的。我们希望 Segment1 中的高吞吐量,但如果执行 Segment2,我们希望停止显示。如果我们尝试执行 Segment2,我们希望保持该行直到完成 Segment1 的执行。希望有帮助。
  • 添加到我上面的“真实世界”场景:调用代码不会独占段 1 - 段 1 可能会在一段时间内受到重击,然后段 2 将被调用,等等。该机制只是为了采取安全措施。

标签: c# .net multithreading design-patterns .net-4.0


【解决方案1】:

这是保护资源免受不当并发访问的一种相当不寻常的模型。我首先会考虑您的用例是否不能转换为可以使用简单锁的等效场景。如果您能提供更多关于为什么需要这种锁定方案的详细信息,那么社区可能会提供其他想法。

为了解决您的具体问题,.NET 甚至 Win32 中都没有直接支持此锁定模型,但是您可以使用其他原语构建它。 我会考虑使用一对ReaderWriterLockSlim 实例来保护每个资源。当线程进入 SegmentA 时,您将获得 A 上的写读锁和 B 上的写锁......反之亦然,线程进入 SegmentB。这将允许多个线程在每个段内执行,但不能同时执行

编辑鉴于您在 cmets 中对您的问题的回复,我更确信您需要考虑使用 Reader/Writer 锁定模型。您是什么寻找是一种保护资源的方法,这样当“作者”正在工作(序列化字典)时,没有读者或其他作者可以进入,而当“读者”正在工作时,他们不会相互阻止,而是阻止所有其他作者.这是读写器锁的经典案例。

编辑 2现在我有更多时间,我认为有一点值得详细说明。考虑锁的方式是它们保护数据资源(内存、文件等)而不是代码区域。我们需要定义一次只有一个线程可以进入的代码的关键部分这一事实是一个实现细节,人们不应将其与共享资源的使用方式(并且必须受到保护)混淆。在您的问题中,关注如何控制哪些线程可以进入哪一部分代码会误导真正的问题:您试图保护哪些数据资源免受哪些类型的更改。一旦你从这个角度看待问题,就会更清楚哪些实现范式是有意义的。

这里有一些关于读写器锁定模型的好资源:

http://msdn.microsoft.com/en-us/magazine/cc163599.aspx

http://msdn.microsoft.com/en-us/library/bz6sth95.aspx

http://blogs.msdn.com/b/vancem/archive/2006/03/29/564854.aspx

【讨论】:

  • 我认为这种不寻常的原因是对 Segment2 的多线程访问。如果我们说一次只有一个线程可以访问 Segment2,但要满足所有其他要求,我认为这只是简化为 ReaderWriterLockSlim,对吗?
  • 它不适用于ReaderWriterLockSlim,原因如下。您不能同时进入写锁。这将成为互斥,最终只有一个线程能够执行 SegmentA
  • 如上所述,这对 ReaderWriterLocks 不起作用。一旦你抓住了写锁,你就阻止了两个线程模拟地执行那个块......
  • @SFun28:是的,我相信这是正确的。但是,我会鼓励这种减少,因为它会让你进入使用锁定的已知领域......而不是在荒野中。
  • @Reed Copsey:是的,我相信你是对的。我会更新我的答案。
【解决方案2】:

鉴于您的编辑,听起来正确的方法是使用ReaderWriterLockSlim,因为您真的不应该在 Segment2 运行时修改集合,并且您不应该允许超过 1 个 Segment2 运行:

private static ReaderWriterLockSlim readerLock = new ReaderWriterLockSlim();
void SomeOperationA()
{
    try
    {  
        readerLock.EnterReadLock(); 
        // Segment1: 
        // ... only executes if no threads are executing in Segment2 ... 
    }    
    finally
    {
        readerLock.ExitReadLock();
    }
}

void SomeOperationB()
{
    try
    {  
        readerLock.EnterWriteLock(); 
        // Prevents multiple Segment2 from serializing, and prevents all Segment1 threads...
    }    
    finally
    {
        readerLock.ExitWriteLock();
    }
}

【讨论】:

  • 他没有说Segment2不能同时执行。如果是真的,简单的lock 将无济于事。
  • @Andrey:是的-我已经编辑包含了-如果这是一个要求,事情会变得有点棘手......
  • 对不起,我的意思是包括那个。是的,两个线程应该能够同时执行Segment1/2(但不能同时执行两个Segment!)
  • SomeOperationB() 不能同时执行。它们的工作方式应该相同。
  • @Andrey:考虑到 OPs 在这里的目标,我不相信这是真的......
【解决方案3】:

有点像。

    class Segments
    {
        public const int None = 0;
        public const int Segm1 = 1;
        public const int Segm2 = 2;
    }

    int currentSegm = 0;
    int segm1counter = 0;
    int segm2counter = 0;

    object segm1lock = new object();
    object segm2lock = new object();

    void SomeOperationA()
    {
        while (
            Interlocked.CompareExchange(ref currentSegm, Segments.Segm1, Segments.Segm1) != Segments.Segm1
            &&
            Interlocked.CompareExchange(ref currentSegm, Segments.Segm1, Segments.None) != Segments.None
            )
        {
            Thread.Yield();
        }

        Interlocked.Increment(ref segm1counter);
        try
        {

            //Segment1: 
            //... only executes if no threads are executing in Segment2 ...                 
        }
        finally
        {
            lock (segm1lock)
            {
                if (Interlocked.Decrement(ref segm1counter) == 0)
                    currentSegm = Segments.None;
            }
        }
    }


    void SomeOperationB()
    {
        while (
            Interlocked.CompareExchange(ref currentSegm, Segments.Segm2, Segments.Segm2) != Segments.Segm2
            &&
            Interlocked.CompareExchange(ref currentSegm, Segments.Segm2, Segments.None) != Segments.None
            )
        {
            Thread.Yield();
        }

        Interlocked.Increment(ref segm2counter);
        try
        {

            //Segment2: 
            //... only executes if no threads are executing in Segment2 ...                 
        }
        finally
        {
            lock (segm2lock)
            {
                if (Interlocked.Decrement(ref segm2counter) == 0)
                    currentSegm = Segments.None;
            }
        }
    }

好的,它不适用于 ReaderWriter 锁定/

【讨论】:

  • 我虽然按照这些思路 - 但写锁阻止了 2 个同时线程,这实际上将其变成了一个“锁”调用。
  • 非常感谢您为我的原始帖子编写解决方案。我现在意识到我最初的场景非常疯狂。
  • @SFun28 这是一个有趣的谜题。我没有深入了解根本原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-18
  • 1970-01-01
相关资源
最近更新 更多