并发与普通集合

本文关键字:集合 并发 | 更新日期: 2023-09-27 18:11:25

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

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

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

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

现在是正常的Queue:

    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和其他一些,但它真正有用的是什么?

任何帮助将非常感激:)

并发与普通集合

不要将lock()ConcurrentQueue<>或该名称空间中的类似项结合使用。这对表现是有害的。

您可以安全地使用ConcurrentQueue<>与多个线程,并有很好的性能。对于lock()和常规集合则不是这样。

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

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

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

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

谢谢大家的回答,真的帮了我大忙,我非常感激。

顺便说一下,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);
    }

再次感谢:)