多線程消費一個隊列問題

問題描述

最近公司有個轉發服務,業務邏輯是從kafka消費到大量的數據,然後放入一個隊列中。之後用一個線程池,去消費這個隊列。

但是發現這四個線程消費隊列的地方又嚴重的延遲。特此想解決此問題。

貼代碼

  • 往隊列裏push數據
void KafkaConsumer::msgConsume(RdKafka::Message* message, void* opaque)
{
	KafkaConsumer::Data cData;
	int errcode = message->err();

	if (errcode == RdKafka::ERR__TIMED_OUT)
	{
		return;
	}
	else if (errcode == RdKafka::ERR_NO_ERROR)  //消費數據,放入隊列
	{
		Data *pData=new Data;
		pData->buffer.writeBlock(static_cast<const char*>(message->payload()),static_cast<int>(message->len())); // payload 裝載,載荷;這裏就是裏面的內容
		//pData->topic = message->topic()->name();  
		pData->topic = message->topic_name();   // 注意這裏
		pData->ipartition = message->partition();

		_cMutex.lock();
		_cDataQue.push(pData); // 放入隊列
		_cMutex.unlock();
	}
	else if (RdKafka::ERR__PARTITION_EOF)
	{
		if (_exit_eof) _run = false;
	}
	else
	{
		LOG(INFO) << "kafkaConsumer--error: Consumer failed:" << message->errstr();
	}
}
  • 取隊列數據,處理篇
void KafkaConsumer::run(void* param)
{
	int tag;
	memcpy(&tag,&param,sizeof(int));
	while (1)
	{
		if (tag == CDATA)
		{
			if(_cDataQue.size() == 0) {
				usleep(2000);
				continue;
			}
			_cMutex.lock();
			while(_cDataQue.size()>0) // 處理一次就都得處理完?!!
			{
				Data *pData = _cDataQue.pop(); // 隊列中取出
				HandleMsg(pData);     // 取數據和處理數據放一起?都在鎖裏?!!
				SAFE_DELETE(pData);
			}
			_cMutex.unlock();
		} else {
			break;
		}
	}
}

代碼錯誤分析

    _cMutex.lock();
            while(_cDataQue.size()>0) // 處理一次就都得處理完?!!
            {
                Data *pData = _cDataQue.pop(); // 隊列中取出
                HandleMsg(pData);     // 取數據和處理數據放一起?都在鎖裏?!!
                SAFE_DELETE(pData);
            }
            _cMutex.unlock();

 線程在數據隊列_cDataQue中的數據時,先上鎖,然後不斷的循環取出隊列中的數據並處理。(取出數據 和處理數據在一起)

處理完每條數據之後delete.

當鎖定時的整個隊列中的數據處理完畢之後,解鎖。

定義幾個變量:

N : 鎖時隊列的長度

T1: pop 一條數據的時間

T2:HandleMsg 函數執行的時間

T3:push 一條數據的時間

此活動中的動作:

1. kafka消費到數據,鎖隊列,寫隊列,解鎖隊列。

2.數據解析線程,鎖隊列,讀數據,解鎖隊列,處理數據。

此時的處理方式,幾乎沒有發揮多線程的優勢,每次都是把鎖時的隊列的全部內容處理完。其他三個線程和生產數據的線程乾等

t = N * (T1+T2) 的時間。 若此時是程序剛啓動。kafka瞬間消費到很多數據成萬條的數據。 那麼t 將是一個很大的時間。且kafka消費到的數據還不能及時的存放如隊列中。於是就造成了延遲。

隱患就是:

1.根本沒發揮多線程的優勢和能力

2.若數據量大,取數據和處理數據放一起,導致鎖態佔用的時間很長,影響其他線程(往queue裏放數據的線程)幹活

3.其他線程競爭不到,乾等,浪費CPU時間。一個線程死幹活,處理不完,數據堆積。延遲。

改進方法

1. 將取數據的地方放在鎖的裏面,處理數據的地方放在鎖的外面。

2.每次取固定數量的nCount 個數據,放在一個容器裏。然後出鎖後慢慢處理。

同時,每次取固定數量的來處理,鎖佔用的時間是固定的,t = nCount * T1 .也就是說,其他3個處理線程和1個往queue裏塞數據的線程。最多隻等 3 * t 的時間就能拿到 queue的控制權,並對其進行操作。

而數據處理的時間 T2 與queue的操作(加鎖,讀取,塞入)沒有關係。

不過要控制nCount的值,太小。鎖的次數很頻繁; 太大,t 的時間會變大。

這樣多線程就用其來了。隊列應用也靈活了。處理能力大大提升。

void KafkaConsumer::run(void* param)
{
	int tag;
	memcpy(&tag,&param,sizeof(int));
	while (1){
		if(_cDataQue.size() == 0) {
			usleep(2000);
			continue;
		}
		std::vector<Data*> vDatas;
		_cMutex.lock();
		while(_cDataQue.size()>0) {//上鎖的時間儘量短,爲其他線程爭取到和寫入線程騰出時間
			Data *pData = _cDataQue.pop(); // 隊列中取出
			vDatas.push_back(pData);
			if(vDatas.size() > 10){ //這裏能限制這個長度 ,最多弄10條。處理快,節省時間。
				break;
			}
		}
		_cMutex.unlock();
		// 將處理移除在鎖之外,慢慢處理這些數據,處理完釋放
		for(vector<Data*>::iterator iter = vDatas.begin(); iter != vDatas.end(); ++iter){
			Data *pData = *iter;
			HandleMsg(pData);
			SAFE_DELETE(pData);
		}	
	}
}

 

用生活實例來解釋描述:

1.角色 : 大廚 (生產者) , 取餐檯/口(queue),包子(數據),顧客(消費處理線程)

2.動作:生產數據(push進queue),取出數據(pop出queue),佔住取餐檯(Lock),放開取餐檯(UNLock),吃包子(HandleMsg)

 

方案一

大廚們生產包子,鎖住取餐口,放下包子。然後顧客1 佔住取餐口,假如這裏有10個包子,他就取一個吃了,再去一個吃了,直到10個取完吃完才離開取餐口。此時,大廚沒法往裏放包子,其他三個顧客都乾等着。

方案二

大廚們生產包子,佔住取餐口,放下包子。顧客1,佔住取餐口,取了10個包子,去一邊吃去。顧客2 ,馬上來也取10個,然後一遍吃去。同理顧客3,4 也一樣。當然這裏只是理想情況,顧客1去完之後,也可能大廚又佔住取餐口,放了1w個包子。

關鍵是,每次取餐口被佔用的時間,之後顧客們取包子的時間。非常短。而且每個顧客取完之後就去一邊吃包子。同時大家可能都在吃包子,實現了多線程處理。


哈哈。就醬紫。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章