并发与普通集合?

时间:2015-09-07 07:38:52

标签: c# multithreading

所以我对System.Collections.Concurrent

有疑问

我看到Concurrent实际上是一个安全的线程集合,但在哪种情况下它可以有用吗?

我做了2个例子,结果是相同的

首先是ConcurrentQueue:

    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和更多,但它真正有用的是什么?

任何帮助都会非常贴切:)

4 个答案:

答案 0 :(得分:2)

不要将lock()ConcurrentQueue<>或该命名空间中的类似项一起使用。这对性能有害。

您可以安全地使用ConcurrentQueue<>多个线程并具有出色的性能。 lock()和常规集合不能说同样的内容。

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

答案 1 :(得分:2)

使用ConcurrentQueue<T>的原因是为了避免编写自己的锁定代码。

如果有多个线程在Queue<T>中添加或删除项目,则可能会出现异常。使用ConcurrentQueue<T>可以避免例外。

这是一个示例程序,当使用多个线程写入Queue<T>时它会与ConcurrentQueue<T>一起使用时可能会导致异常:

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);
    }
}

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

谢谢大家的所有答案,真的帮助了我,我很好地批评了它。

顺便说一下Matthew Watson,你的例子有时会给出一个例外,有时候不是,我做了一个更好的例子,但是我明白了。

    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);
    }

再次感谢:)