【问题标题】:Threadsafe collection without lock没有锁的线程安全集合
【发布时间】:2012-05-27 08:55:48
【问题描述】:

我正在准备面试,我遇到了以下问题。我试过了,但我找不到任何东西可以创建一个包含没有“锁”的线程安全集合的类。如果知道任何解决方案,请提供帮助。

创建一个从 Object 派生的 C# 类并实现以下方法:

  • AddString – 此方法应将给定字符串添加到内部集合中
  • ToString – 覆盖此方法并返回一个以逗号分隔的字符串,其中包含内部集合中的所有字符串

要求:

  • 必须是线程安全的
  • 必须支持多个并发阅读器
  • 不得使用任何预先存在的线程安全集合
  • 奖励:不要使用任何类型的锁

【问题讨论】:

  • 这个问题要求能够模仿lock 的功能,你确定你会面试一个需要的职位吗?

标签: c# multithreading collections


【解决方案1】:

这是一种实现对集合进行无锁修改的方法,方法是处理本地副本,然后在检查竞争时尝试以原子方式将其与全局集合交换:

public class NonLockingCollection
{
    private List<string> collection;

    public NonLockingCollection()
    {
        // Initialize global collection through a volatile write.
        Interlocked.CompareExchange(ref collection, new List<string>(), null);
    }

    public void AddString(string s)
    {
        while (true)
        {
            // Volatile read of global collection.
            var original = Interlocked.CompareExchange(ref collection, null, null);

            // Add new string to a local copy.
            var copy = original.ToList();
            copy.Add(s);

            // Swap local copy with global collection,
            // unless outraced by another thread.
            var result = Interlocked.CompareExchange(ref collection, copy, original);
            if (result == original)
                break;
        }
    }

    public override string ToString()
    {
        // Volatile read of global collection.
        var original = Interlocked.CompareExchange(ref collection, null, null);

        // Since content of global collection will never be modified,
        // we may read it directly.
        return string.Join(",", original);
    }
}

编辑:由于使用Interlocked.CompareExchange 隐式执行易失性读取和写入会引起一些混乱,因此我将在等效代码下方发布Thread.MemoryBarrier 调用。

public class NonLockingCollection
{
    private List<string> collection;

    public NonLockingCollection()
    {
        // Initialize global collection through a volatile write.
        collection = new List<string>();
        Thread.MemoryBarrier();
    }

    public void AddString(string s)
    {
        while (true)
        {
            // Fresh volatile read of global collection.
            Thread.MemoryBarrier();
            var original = collection;
            Thread.MemoryBarrier();

            // Add new string to a local copy.
            var copy = original.ToList();
            copy.Add(s);

            // Swap local copy with global collection,
            // unless outraced by another thread.
            var result = Interlocked.CompareExchange(ref collection, copy, original);
            if (result == original)
                break;
        }
    }

    public override string ToString()
    {
        // Fresh volatile read of global collection.
        Thread.MemoryBarrier();
        var original = collection;
        Thread.MemoryBarrier();

        // Since content of global collection will never be modified,
        // we may read it directly.
        return string.Join(",", original);
    }
}

【讨论】:

  • 不完全;这是强制执行易失性读取的常用方法。类似于隐式Thread.MemoryBarrier()
  • 您的代码似乎对CompareExchange() 的结果不太感兴趣。你知道它可能会“失败”。
  • @HenkHolterman:如果失败,引用相等检查result == original 将返回false,导致重新尝试整个循环。
  • 是的,我必须阅读 2 次。这东西总是让我头疼。我现在认为它可能会起作用,但我不会用 3m 的杆子碰它。当然,ToList() 会很快使这比最重的 Sync 对象更糟。
  • FWIW,.ToList 在现有的List&lt;T&gt; 上使用时表现相当不错,因为长期存在微优化。它有效地使用了 Array.Copy,它使用了从这种优化中受益的外部本机实现。问题的“逻辑”复杂性与实际系统上的性能不同,因为不同的操作会产生显着不同的执行时间,所以不要过分依赖严格的理论 Big-O 表示法。例如:dzone.com/articles/…
【解决方案2】:

根据问题,您应该能够在对象中添加一个并发集合,该集合将为您处理线程安全要求。他们没有指定使用哪种类型的内部集合。

您应该能够从 concurrentcollection 命名空间实现其中一个集合并实现这一点。

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

【讨论】:

  • 最佳答案,至少对我而言
【解决方案3】:

您可以创建一个非阻塞链表。例如this

.net 框架提供了像CompareExchange(Object, Object, Object) 这样的方法,允许您编写安全代码而无需锁定整个列表。

【讨论】:

    【解决方案4】:

    我的解决方案。基本上使用 Interlocked.Exchange 和 AutoResetEvents 模拟锁定。做了一些简单的测试,它似乎工作。

        public class SharedStringClass
        {
            private static readonly int TRUE = 1;
            private static readonly int FALSE = 0;
    
            private static int allowEntry;
    
            private static AutoResetEvent autoResetEvent;
    
            private IList<string> internalCollection;
    
            public SharedStringClass()
            {
                internalCollection = new List<string>();
                autoResetEvent = new AutoResetEvent(false);
                allowEntry = TRUE;
            }
    
            public void AddString(string strToAdd)
            {
                CheckAllowEntry();
    
                internalCollection.Add(strToAdd);
    
                // set allowEntry to TRUE atomically
                Interlocked.Exchange(ref allowEntry, TRUE);
                autoResetEvent.Set();
            }
    
            public string ToString()
            {
                CheckAllowEntry();
    
                // access the shared resource
                string result = string.Join(",", internalCollection);
    
                // set allowEntry to TRUE atomically
                Interlocked.Exchange(ref allowEntry, TRUE);
                autoResetEvent.Set();
                return result;
            }
    
            private void CheckAllowEntry()
            {
                while (true)
                {
                    // compare allowEntry with TRUE, if it is, set it to FALSE (these are done atomically!!)
                    if (Interlocked.CompareExchange(ref allowEntry, FALSE, TRUE) == FALSE)
                    {
                        autoResetEvent.WaitOne();
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    

    【讨论】:

      【解决方案5】:

      最简单的解决方案是使用string[] 类型的字段。每当调用者想要添加一个字符串时,创建一个新数组并附加新项并将其交换为旧项。

      此模型不需要同步。它不容忍并发写入者,但允许并发读取。

      【讨论】:

        猜你喜欢
        • 2014-02-25
        • 2011-06-09
        • 1970-01-01
        • 2013-12-10
        • 1970-01-01
        • 2015-02-08
        • 2011-04-07
        • 1970-01-01
        • 2011-02-28
        相关资源
        最近更新 更多