双缓冲队列来减少锁的竞争

双缓冲队列来减少锁的竞争

在日常的开发中,日志的记录是必不可少的。但是我们也清楚对同一个文本进行写日志只能单线程的去写,那么我们也经常会使用简单lock锁来保证只有一个线程来写入日志信息。但是在多线程的去写日志信息的时候,由于记录日志信息是需要进行I/O交互的,导致我们占用锁的时间会加长,从而导致大量线程的阻塞与等待。

  这种场景下我们就会去思考,我们该怎么做才能保证当有多个线程来写日志的时候我们能够在不利用锁的情况下让他们依次排队去写呢?这个时候我们就可以考虑下使用双缓冲队列来完成。

  所谓双缓冲队列就是有两个队列,一个是用来专门负责数据的写入,另一个是专门负责数据的读取,当逻辑线程读取完数据后负责将自己的队列与I/O线程的队列进行交换。

  我们该怎么利用这双缓冲队列来完成我们想要的效果呢?

  当有多个线程来写日志的时候,这个时候我们要这些要写的信息先放到我们负责写入的队列当中,然后将逻辑读的线程设为非阻塞。此时逻辑读的线程就可以开始工作了。(一开始时逻辑读的队列是空的)在当逻辑读的线程读取他自己队列的数据(并执行一些逻辑)之后,将逻辑读的队列的引用和负责写入的队列进行引用交换。这就是简单的一个双缓冲队列实现的一个思路。具体实现代码如下:

  

 User

    public class DoubleQueue {
        private ConcurrentQueue<User> _writeQueue;
        private ConcurrentQueue<User> _readQueue;
        private volatile ConcurrentQueue<User> _currentQueue;

        private AutoResetEvent _dataEvent;
        private ManualResetEvent _finishedEvent;
        private ManualResetEvent _producerEvent;

        public DoubleQueue() {
            _writeQueue = new ConcurrentQueue<User>();
            _readQueue = new ConcurrentQueue<User>();
            _currentQueue = _writeQueue;

            _dataEvent = new AutoResetEvent(false);
            _finishedEvent = new ManualResetEvent(true);
            _producerEvent = new ManualResetEvent(true);
            Task.Factory.StartNew(() => ConsumerQueue(), TaskCreationOptions.None);
        }

        public void ProducerFunc(User user) {
            _producerEvent.WaitOne();
            _finishedEvent.Reset();
            _currentQueue.Enqueue(user);
            _dataEvent.Set();
            _finishedEvent.Set();
        }

        public void ConsumerQueue() {
            ConcurrentQueue<User> consumerQueue;
            User user;
            int allcount = 0;
            Stopwatch watch = Stopwatch.StartNew();
            while (true)
            {
                _dataEvent.WaitOne();
                if (_currentQueue.Count > 0)
                {
                    _producerEvent.Reset();
                    _finishedEvent.WaitOne();
                    consumerQueue = _currentQueue;
                    _currentQueue = (_currentQueue == _writeQueue) ? _readQueue : _writeQueue;
                    _producerEvent.Set();
                    while (consumerQueue.Count > 0)
                    {
                        if (consumerQueue.TryDequeue(out user))
                        {
                            FluentConsole.White.Background.Red.Line(user.ToString());
                            allcount++;
                        }
                        FluentConsole.White.Background.Red.Line($"当前个数{allcount.ToString()},花费了{watch.ElapsedMilliseconds.ToString()}ms;");
                        System.Threading.Thread.Sleep(20);
                    }
                }
            }
        }
    }

FluentConsole 是一个控制台应用程序的输出插件,开源的,有兴趣的可以自己去玩玩。

 输出端代码

第一个利用双缓冲队列来执行,第二个利用lock锁来执行。下面分别是第一种方法和第二种方法执行时CPU的消耗。

我们可以发现利用双队列缓冲的情况下我们减少了CPU的占有。但是我们可能会增加执行的时间。

参考文章:http://www.codeproject.com/Articles/27703/Producer-Consumer-Using-Double-Queues

别人在08年就已经想到了,而我却在现在才稍微有点想法。

源码下载

时间: 2024-08-01 14:22:21

双缓冲队列来减少锁的竞争的相关文章

利用双缓冲队列来减少锁的竞争

在日常的开发中,日志的记录是必不可少的.但是我们也清楚对同一个文本进行写日志只能单线程的去写,那么我们也经常会使用简单lock锁来保证只有一个线程来写入日志信息.但是在多线程的去写日志信息的时候,由于记录日志信息是需要进行I/O交互的,导致我们占用锁的时间会加长,从而导致大量线程的阻塞与等待. 这种场景下我们就会去思考,我们该怎么做才能保证当有多个线程来写日志的时候我们能够在不利用锁的情况下让他们依次排队去写呢?这个时候我们就可以考虑下使用双缓冲队列来完成. 所谓双缓冲队列就是有两个队列,一个是

双缓冲队列-减少生产者消费者锁的调用

在生产者-消费者模式中,我们常常会使用到队列,这个队列在多个线程共享访问时存在互斥和竞争操作, 意味着每次访问都要加锁.如何更好的如何减少锁竞争次数呢 ?今天要介绍的双缓冲队列就是个不错的选择. 双缓冲队列就是冲着同步/互斥的开销来的.我们知道,在多个线程并发访问同一个资源的时候,需要特别注意线程的同步问题.稍稍不注意,噢货,程序结果不正确了. 原理 直接上图: 这样为什么会减少锁的调用呢? 举个例子, 两个List,一个用来存,一个用来取.有点迷糊?就是有一个listP从工厂那里得到玩具对象,

多线程操作C++ STL vector出现概率coredump问题及尽量避免锁的双缓冲队列

多线程操作全局变量,必须考虑同步问题,否则可能出现数据不一致, 甚至触发coredump. 前段时间, 遇到一个多线程操作了全局的vector的问题,  程序崩了.场景是这样的:某全局配置参数保存在一个vector中,需要定时更新(更新线程), 另外的工作线程去读取配置. 这种场景是非常普遍的. 在该场景中,程序没有枷锁,概率coredump, 实际情况是,服务跑了一段时间后,必然coredump.   很显然, 更新线程执行clear,然后在push_back操作时, 会导致工作线程的vect

服务器应用--双缓冲队列

在服务器开发中 通常的做法是 把 逻辑处理线程和I/O处理线程分离. 逻辑处理线程:对接收的包进行逻辑处理. I/0处理线程:网络数据的发送和接收,连接的建立和维护. 通常 逻辑处理线程和I/O处理线程是通过数据队列来交换数据,就是生产者--消费者模型. 这个数据队列是多个线程在共享,每次访问都需要加锁,因此如何减少 互斥/同步的开销就显得尤为重要.                                                                         解

HashMap并发出现死循环 及 减少锁的竞争

线程不安全的HashMap, HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,查找时会陷入死循环. https://www.cnblogs.com/dongguacai/p/5599100.html https://coolshell.cn/articles/9606.html 减少锁的竞争3种方法: (1)减少锁的持有时间(缩小锁的范围) (2)降低锁的请求频率(降低锁的粒度) (3)放弃使用独占锁,使用并发容器,原子变量,读

双缓冲List

前言 声明:本文从Enode之父汤雪华QQ群里提取的,分享给各位小伙伴们供参考. 双缓冲,顾名思义为两个List,此设计用于解决收发分离过程中最大限度避免锁的竞争.有效的降低CPU开销.降低锁的竞争冲突而生. 应用场景 双缓冲List的设计思路,应用在多线程产生消息,单线程处理消息的场景. 设计思路 声明2个list,分别为_requestsWrite,_requestsRead,写入的消息放入_requestsWrite,读取从_requestsRead读取: 再声明一个bool类型的_has

C++11 实现生产者消费者双缓冲

基础的生产者消费者模型,生产者向公共缓存区写入数据,消费者从公共缓存区读取数据进行处理,两个线程访问公共资源,加锁实现数据的一致性. 通过加锁来实现 1 class Produce_1 { 2 public: 3 Produce_1(std::queue<int> * que_, std::mutex * mt_) { 4 m_mt = mt_; 5 m_que = que_; 6 m_stop = false; 7 } 8 void runProduce() { 9 while (!m_st

高并发下减少锁竞争

1.减少锁的持有时间,将不需要锁的操作从同步代码块的移除. //可以优化的代码 class AttributeStore{ private final Map<String,String> attributes=new HashMap<String,String>(); public synchronized boolean userLocationMatches(String username,String regex){ String key="user."

c语言多线程缓冲队列无锁设计思路

公司里开发的一个项目需要在server端添加多线程缓冲队列,来存取数据,我也是初出茅庐没有太多经验,在网上搜集了大量资料后,终于有了一套自己的设计思路,并解决了项目里的问题,因为当时搜集资料时,发现网上这个的具体文章不是太多或者要么太复杂,要么太简陋,对于新手很难能看懂,所有我就打算将我的设计思路发出来,希望能帮助和我一样的同学朋友们,如有不足请指导!谢谢 项目需求: 两个线程,线程1接收客户端数据并有序的存入缓冲队列:线程2从缓冲队列有序的取出数据并解析插入数据库: 解决方法: 1:新建一个结