• c++千万数据级别正确使用无锁队列,避免内存撕碎(一)


    1、 内存

    2、时间计算类

    这个时间计算在我以前的文章里面都有

    class TicToc
    {
    public:
    	TicToc()
    	{
    		tic();
    	}
    
    	void tic()
    	{
    		start = std::chrono::system_clock::now();
    	}
    
    	double toc()
    	{
    		end = std::chrono::system_clock::now();
    		std::chrono::duration<double> elapsed_seconds = end - start;
    		start = end;
    		return elapsed_seconds.count() * 1000;
    	}
    
    private:
    	std::chrono::time_point<std::chrono::system_clock> start, end;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    还是依照以前的习惯,写好时间计算类

    3、定义数据结构

    struct s_data
    {
    	uint8_t* data = NULL;
    	size_t len = 0;
    	s_data(const char* in)
    	{
    		len = strlen(in);
    		data = (uint8_t*)malloc(len+1);
    		if (data == NULL)
    			return;
    		memcpy(data, in, len);
    		data[len] = '\0';
    	}
    	~s_data()
    	{
    		if (data != NULL)
    			free(data);
    	}
    };
    typedef std::shared_ptr<s_data> ptr_s_data;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们之所以使用智能指针,是希望程序直接释放,在不需要的时候直接内存放弃

    4、boost 无锁队列

    boost 无锁数据结构:以下摘自于boost文档
    boost.lockfree implements three lock-free data structures:

    boost::lockfree::queue
    a lock-free multi-produced/multi-consumer queue

    boost::lockfree::stack
    a lock-free multi-produced/multi-consumer stack

    boost::lockfree::spsc_queue
    a wait-free single-producer/single-consumer queue (commonly known as ringbuffer)

    下面我们使用spsc_queue

    boost::lockfree::spsc_queue > spsc_queue;

    1、这是个单生产者,单消费者,多生产者,多消费者用处比较少,这里不使用,且以性能来说,spsc_queue的性能要高。高性能的程序不在于线程使用很多,而在于使用线程和内存以及cpu,gpu的合理性。
    2、里面的100是指的程序缓冲,但值得注意的是:这个缓冲我们使用的是指针,而不是真实的数据空间。

    使用指针空间意味着内存在外分配,我们成为带外分配,也就意味着内存容易撕裂。那我们应该怎么正确分配内存?一般来说,程序都会使用自己的内存管理,分配一个较大的内存,从中取得内存使用权限,有一个再分配管理。

    int main()
    {
    	if (spsc_queue.is_lock_free())
    		std::cout << "single producer, single consume" << std::endl;
    	//1000 second;// 000
    	TicToc tp;
    	std::thread thread_producer([=,&tp]() {
    		//every ten millonsecond to 
    		int insert_num = 0;
    		for (;;)
    		{
    			if (spsc_queue.write_available())
    			{
    				char buffer[128];
    				sprintf(buffer, "this is test %d\n", insert_num);
    				ptr_s_data obj = std::make_shared<s_data>(buffer);
    				spsc_queue.push(obj);
    				if (insert_num++ > 100000)
    					break;
    				//condition_producers.notify_one();
    			}
    			else
    				boost::this_thread::sleep(1);
    		}
    		
    		
    		std::cout << tp.toc()<< " the insert is over " << std::endl;
    
    	});
    
    	std::thread thread_consume([]() {
    
    		TicToc tc;
    		while (!done)
    		{
    			std::unique_lock<std::mutex> ul(pro_mtx);
    			//condition_producers.wait(ul);// , [] {
    			if (spsc_queue.read_available() > 0)
    			{
    				ptr_s_data obj = nullptr;
    				spsc_queue.pop(obj);
    				ncount++;
    				if (obj && obj->data != NULL)
    				{
    					if ((ncount % 10000) == 0)
    						std::cout << obj->data;
    				}
    			}
    			else
    				lee_mill(2);
    		}
    		std::cout << "left is " << spsc_queue.read_available() << std::endl;
    		std::cout << tc.toc() << " consume is over" << std::endl;
    	});
    
    	thread_producer.join();
    	done = true;
    	thread_consume.join();
    
    	return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    以下是执行的结果,看样子left是零,不过是巧合,生产者线程和消费者线程比较均衡。所以left 多少是与cpu 线程执行有关。剩余100 个,也是有可能的。读者可以自己调整,让其没有剩余,就是在退出循环之后,继续把剩余的执行完毕,然我们的目的不是这个,主要是为了哟用自己的内存管理单元,我们称之为mmu在这里插入图片描述
    上面的程序是直接分配内存,删除内存,并没有内存管理,那么如果做到内存管理呢,我们必须制作自己的mmu。

    5、mmu内存管理单元

    这是我前面的内存池文章
    上面这篇文章讨论了内存池的制作,我们使用mmu,就是要在内存池上更上一个台阶,能够达到管理的作用,这个下一节再讲了

  • 相关阅读:
    Java实现魔板拼图小游戏(完整版)
    宏转录组分析揭示不同土壤生境中氮循环基因的表达
    音视频从入门到精通——FFmpeg分离出PCM数据实战
    WPF MVVM模式下如何给Textbox设置焦点(GalaSoft.MvvmLight)
    Simulink与Arduino烧录配置
    Docker-compose
    码蹄集 - MT2322 - 还是跑图:还是简单图问题
    uboot启动流程-涉及_main汇编函数
    文心一言初体验,和ChatGPT语言理解能力比较
    【建议收藏】回收站数据恢复如何操作?3个方案帮你恢复删除的文件
  • 原文地址:https://blog.csdn.net/qianbo042311/article/details/125868037