【问题标题】:Concurrency vs Normal Collection?并发与普通收集?
【发布时间】:2015-09-07 07:38:52
【问题描述】:

所以我有一个关于 System.Collections.Concurrent 的问题

我看到 Concurrent 实际上是一个安全的线程集合,但在哪些情况下它会有所帮助?

我做了2个例子,结果是一样的

首先是并发队列:

    static ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
    private static readonly object obj = new object();
    static int i = 0;
    static int Num = 0;
    static void Run(object loopNum)
    {
        lock (obj)
        {
            for (int N = 0; N < 10; N++)
            {
                queue.Enqueue (i);
                Thread.Sleep(250);
                queue.TryDequeue(out Num);
                Console.WriteLine($"{Num} Added! in {loopNum} Loop, ThreadID: [{Thread.CurrentThread.ManagedThreadId}]");
                i++;
            }
        }
    }

现在是正常的队列:

    static Queue<int> queue = new Queue<int>();
    private static readonly object obj = new object();
    static int i = 0;

    static void Run(object loopNum)
    {
        lock (obj)
        {
            for (int N = 0; N < 10; N++)
            {
                queue.Enqueue (i);
                Thread.Sleep(250);
                Console.WriteLine($"{queue.Dequeue()} Added! in {loopNum} Loop, ThreadID: [{Thread.CurrentThread.ManagedThreadId}]");
                i++;
            }
        }
    }

主要:

    static void Main()
    {
        Thread[] Th = new Thread[] { new Thread(Run), new Thread(Run) };
        Th[0].Start("First");
        Th[1].Start("Second");


        Console.ReadKey();
    }

结果是一样的

当然,它有一些不同的方法,比如 TryDequeue 等等,但它真的有什么用呢?

任何帮助都会非常感激:)

【问题讨论】:

  • 在多个线程上同时访问集合时很有用。
  • 在尝试测量不同线程安全机制之间的时间差异时,添加 250 毫秒的延迟可能会超过该差异几个数量级......

标签: c# multithreading


【解决方案1】:

不要将lock()ConcurrentQueue&lt;&gt; 或该命名空间中的类似项目结合使用。这对性能不利。

您可以在多个线程中安全地使用ConcurrentQueue&lt;&gt;,并具有出色的性能。 lock() 和常规收藏不能这样说。

这就是为什么你的结果是一样的。

【讨论】:

  • 请注意,访问共享资源的其他代码也必须是线程安全的——在这种情况下,用Interlocked.Increment(ref i) 替换i++ 就可以了。当然,Thread.Sleep 中的 250 毫秒等待可能会使使用简单队列的两个线程的性能差异相形见绌……添加更多线程并摆脱 Thread.Sleep 将描绘出更清晰的画面。
【解决方案2】:

使用ConcurrentQueue&lt;T&gt;的原因是为了避免编写自己的锁定代码。

如果您有多个线程在Queue&lt;T&gt; 中添加或删除项目,您可能会遇到异常。使用ConcurrentQueue&lt;T&gt; 将避免异常。

这是一个示例程序,当它与ConcurrentQueue&lt;T&gt;一起工作时,使用多个线程写入Queue&lt;T&gt;时可能会导致异常:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

internal class Program
{
    private static void Main()
    {
        var queue1 = new ConcurrentQueue<int>();
        var queue2 = new Queue<int>();

        // This will work fine.

        var task1 = Task.Run(() => producer(item => queue1.Enqueue(item)));
        var task2 = Task.Run(() => producer(item => queue1.Enqueue(item)));

        Task.WaitAll(task1, task2);

        // This will cause an exception.

        var task3 = Task.Run(() => producer(item => queue2.Enqueue(item)));
        var task4 = Task.Run(() => producer(item => queue2.Enqueue(item)));

        Task.WaitAll(task3, task4);
    }

    private static void producer(Action<int> add)
    {
        for (int i = 0; i < 10000; ++i)
            add(i);
    }
}

尝试运行它,看看会发生什么。

【讨论】:

    【解决方案3】:

    当您使用lock 构造时,您的代码有效地按顺序执行,而不是并行执行。此解决方案适用于具有简单Queue 的版本,因为它不是线程安全的,但使用ConcurrentQueue,使用lock 有点达不到目的。移除ConcurrentQueue 的锁,移除Thread.Sleep,并使用20 个线程而不是2 个线程。您可以使用Parallel.For() 方法来生成您的任务。

    Parallel.For(0, 20, i => Run());
    

    【讨论】:

      【解决方案4】:

      谢谢大家的回答,真的帮了我很多,我很感激。

      顺便说一句,马修·沃森,你的例子有时是例外,有时不是,我做了一个更好的例子,但是我明白了。

          private static void Main()
          {
              var queue1 = new ConcurrentQueue<int>();
              var queue2 = new Queue<int>();
      
              // This will work fine.
      
              var task1 = Enumerable.Range(0, 40)
                  .Select(_ => Task.Run(() => producer(item => queue1.Enqueue(item))))
                  .ToArray();
      
              Task.WaitAll(task1);
      
              // This will cause an exception.
      
              var task2 = Enumerable.Range(0, 40)
                              .Select(_ => Task.Run(() => producer(item => queue2.Enqueue(item))))
                              .ToArray();
      
              Task.WaitAll(task2);
          }
      

      再次感谢:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-18
        • 1970-01-01
        • 2011-08-05
        • 1970-01-01
        • 1970-01-01
        • 2023-03-28
        • 2019-08-14
        相关资源
        最近更新 更多