线程安全集合之BlcokingCollection

一、前言

有这么一个场景,我有一个socket接收来自一个服务器的消息,然后处理这些消息。为了减小丢包的概率,我想到了这么一个思路:一个线程专门接收消息,接收到的接收先存入队列,另外开一个线程从队列中取消息处理。

这就涉及到了多线程安全问题,我的第一反应就想到了使用ConcurrentQueue类。

二、ConcurrentQueue类

我们先不说使用这个类会遇到什么问题,先还原一下场景,代码如下:

public partial class MainWindow : Window
    {
        ConcurrentQueue<string> queue = new System.Collections.Concurrent.ConcurrentQueue<string>();

        public MainWindow()
        {
            InitializeComponent();

            Task.Run(Add);
            Task.Run(Take);
        }


        private void Add()
        {
            int i = 0;
            while (true)
            {
                try
                {
                    queue.Enqueue(i.ToString());
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                Thread.Sleep(5000);
                i++;
            }
        }

        private void Take()
        {
            while (true)
            {
                if (queue.TryDequeue(out string item))
                {
                    Console.WriteLine($"take one item:{item}");
                }
            }
        }
    }

上述代码应该很简单易懂,不做过多解释。

三、ConcurrentQueue类遇到的问题

我们重点看那个Task函数,如果有一种情况:当Task函数的处理速度大于Add的时候,那么大多数时间调用TryDequeue为false,则不会进入消息处理环节,会再次进行无间隔的TryDequeue。这种情况查看CPU占用率,我们会发现比较高10%~20%,这显然是不正常的。

显然问题就出在TryDequeue那里,当我们调用这个返回false的时候,那么会无间隔再次尝试,如果绝大多数时间总是返回false,那么绝大多数时间就在那无间隔调用TryDequeue。那我们加一个延时阻塞一下是不是会好一些呢???

private void Take()
        {
            while (true)
            {
                if (queue.TryDequeue(out string item))
                {
                    Console.WriteLine($"take one item:{item}");
                }
                else
                    Thread.Sleep(1);
            }
        }

果然,我们在返回false的时候阻塞1 ms,再次查看CPU占用发现已经在0%附近了。那么我们的问题时候解决了呢???

虽然阻塞了1ms起了作用,但是阻塞1ms后还是会继续调用TryDequeue,还是进行了不必要的操作。

那么会不会有一个东西的存在呢?当返回false的时候回自己阻塞在那,当可以Dequeue成功的时候再去TryDequeue。

三、Blocking Collection类

BlockingCollection 是一个线程安全集合类,可提供以下功能:

  • 实现制造者-使用者模式。
  • 通过多线程并发添加和获取项。
  • 可选最大容量。
  • 集合为空或已满时通过插入和移除操作进行阻塞。
  • 插入和移除“尝试”操作不发生阻塞,或在指定时间段内发生阻塞。
  • 封装实现 IProducerConsumerCollection 的任何集合类型
  • 使用取消标记执行取消操作。
  • 支持使用 foreach(在 Visual Basic 中,使用 For Each)的两种枚举:
    1. 只读枚举。
    2. 在枚举项时将项移除的枚举。

在针对有限集合的计时阻塞 TryAddTryTake 操作中,此方法将尝试添加或取出某个项。 如果项可用,项会被置于通过引用传入的变量中,然后方法返回 true。 如果在指定的超时期限过后未检索到任何项,方法返回 false。 相应线程可以任意执行一些其他有用的工作,然后再重新尝试访问该集合。

如果没有任何项、已达到绑定集合的最大容量或已超过超时期限,TryAddTryTake 操作返回 false。 这样一来,线程可以暂时执行其他一些有用的工作,并在稍后再次尝试检索新项,或尝试添加先前无法添加的相同项。

public partial class MainWindow : Window
    {
        BlockingCollection<string> queue = new BlockingCollection<string>(10);

        public MainWindow()
        {
            InitializeComponent();

            Task.Run(Add);
            Task.Run(Take);
        }


        private void Add()
        {
            int i = 0;
            while (true)
            {
                try
                {
                    if (queue.TryAdd(i.ToString(),-1))
                        Console.WriteLine($"add one item:{i}");
                    else
                        Console.WriteLine($"add one item:{i} fail");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
               //Thread.Sleep(5000);
                i++;
            }
        }

        private void Take()
        {
            while (true)
            {
                if (queue.TryTake(out string item, -1))
                {
                    Console.WriteLine($"take one item:{item}");
                }
                else
                    Console.WriteLine($"take one item:{item} fail");
                Thread.Sleep(5000);
            }
        }

代码如上:当集合已达到最大容量的时候,TryAdd线程会阻塞在那里,直到容量小于最大容量后,才会继续添加到集合中。当集合为空的时候,TryTake线程也会阻塞在那里,直到集合不为空的时候,才会继续取出元素。阻塞的时候,可以自己设置,当为-1的时候表示永久阻塞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值