我實際上正在尋找一種方法來在我的C++中執行異步和線程安全的日誌記錄。C++中的異步線程安全日誌記錄(無互斥體)
我已經探索了像log4cpp,log4cxx,Boost:log或rlog這樣的線程安全日誌解決方案,但似乎所有這些都使用互斥鎖。據我所知,互斥是一個同步解決方案,這意味着所有的線程都被鎖定,因爲他們試圖寫他們的消息,而其他的。
你知道一個解決方案嗎?
我實際上正在尋找一種方法來在我的C++中執行異步和線程安全的日誌記錄。C++中的異步線程安全日誌記錄(無互斥體)
我已經探索了像log4cpp,log4cxx,Boost:log或rlog這樣的線程安全日誌解決方案,但似乎所有這些都使用互斥鎖。據我所知,互斥是一個同步解決方案,這意味着所有的線程都被鎖定,因爲他們試圖寫他們的消息,而其他的。
你知道一個解決方案嗎?
在Windows程序中,我們使用用戶定義的Windows消息。首先,內存分配給堆上的日誌條目。然後調用PostMessage,指針爲LPARAM,記錄大小爲WPARAM。接收器窗口提取記錄,顯示記錄,並將其保存在日誌文件中。然後PostMessage返回,分配的內存由發送者解除分配。這種方法是線程安全的,您不必使用互斥鎖。併發由Windows的消息隊列機制處理。 不是非常優雅,但工程。
Windows消息隊列實際上是否確保其併發處理方案?我敢打賭它不是無鎖的。 – thiton
它是我的,也可能不是,無鎖。更重要的是,與任何'正常的'用戶空間P-C隊列相比,它是緩慢的(例如,一個CS保護它的隊列和一個阻塞的信號量)。 –
OTOH,比WMQ差 - IOCP隊列更慢!至少有一個WMQ是一個比模板更好的解決方案(讀作「過度和可避免的數據複製」),每個生產者線程的無鎖隊列都必須由消費者調查。 –
就我所知,沒有庫會這樣做 - 這太複雜了。你必須推出你自己的,這裏有一個我剛纔的想法,創建每個線程日誌文件,確保每個條目中的第一項是時間戳,然後合併日誌,然後運行和排序(通過時間戳)來獲得最終的日誌文件。
你可以使用一些線程本地存儲可能(說一個FILE
句柄AFAIK它將不可能存儲在線程本地存儲一個流對象),並在每個日誌行上查看這個句柄,並寫入該特定文件。
所有這些複雜性與鎖定互斥?我不知道你的應用程序的性能要求,但如果它是敏感的 - 爲什麼你會記錄(過分)?想想其他方式來獲取你所需要的信息而不需要記錄?
還有一件事要考慮的是儘可能少的時間使用互斥鎖,即先構造您的日誌條目,然後在寫入文件之前獲取鎖。
我會推薦避免使用只有一個線程進行日誌記錄的問題。爲了將必要的數據傳遞給日誌,可以使用無鎖fifo隊列(線程安全,只要生產者和消費者嚴格分開,並且每個角色只有一個線程 - 因此每個生產者需要一個隊列。)
快速無鎖隊列的實例包括:
queue.h:
#ifndef QUEUE_H
#define QUEUE_H
template<typename T> class Queue
{
public:
virtual void Enqueue(const T &element) = 0;
virtual T Dequeue() = 0;
virtual bool Empty() = 0;
};
hybridqueue.h:
#ifndef HYBRIDQUEUE_H
#define HYBRIDQUEUE_H
#include "queue.h"
template <typename T, int size> class HybridQueue : public Queue<T>
{
public:
virtual bool Empty();
virtual T Dequeue();
virtual void Enqueue(const T& element);
HybridQueue();
virtual ~HybridQueue();
private:
struct ItemList
{
int start;
T list[size];
int end;
ItemList volatile * volatile next;
};
ItemList volatile * volatile start;
char filler[256];
ItemList volatile * volatile end;
};
/**
* Implementation
*
*/
#include <stdio.h>
template <typename T, int size> bool HybridQueue<T, size>::Empty()
{
return (this->start == this->end) && (this->start->start == this->start->end);
}
template <typename T, int size> T HybridQueue<T, size>::Dequeue()
{
if(this->Empty())
{
return NULL;
}
if(this->start->start >= size)
{
ItemList volatile * volatile old;
old = this->start;
this->start = this->start->next;
delete old;
}
T tmp;
tmp = this->start->list[this->start->start];
this->start->start++;
return tmp;
}
template <typename T, int size> void HybridQueue<T, size>::Enqueue(const T& element)
{
if(this->end->end >= size) {
this->end->next = new ItemList();
this->end->next->start = 0;
this->end->next->list[0] = element;
this->end->next->end = 1;
this->end = this->end->next;
}
else
{
this->end->list[this->end->end] = element;
this->end->end++;
}
}
template <typename T, int size> HybridQueue<T, size>::HybridQueue()
{
this->start = this->end = new ItemList();
this->start->start = this->start->end = 0;
}
template <typename T, int size> HybridQueue<T, size>::~HybridQueue()
{
}
#endif // HYBRIDQUEUE_H
+1:無鎖隊列和讀線程確實是要走的路 –
什麼?每個生產者一個隊列 - 如何管理?無論如何,這個隊列是如何工作的 - 當隊列爲空時,生產者等待什麼?一個記錄程序線程,一個PC隊列和一個只在隊列推送周圍的鎖,(即隊列只鎖定一個對象實例所需的時間(即32/64位參考值),on,對我來說似乎更清晰 –
你可以擁有這些隊列的向量或映射,你可以在這裏給隊列分配隊列,消費者只需遍歷整個隊列隊列,生產者不會等待,生產者只需要排隊更多的數據:P消費者可以被放置睡覺,做一些其他的工作,或者OP可以引入一些推動的信號機制,這個類可以很容易地修改。至於鎖定,OP想要避免這種情況。 – Erbureth
如果我得到你的問題右擊你關心在記錄器的關鍵部分做I/O操作(可能寫入文件)。
Boost:log允許您定義自定義編寫器對象。您可以定義operator()來調用異步I/O或將消息傳遞到日誌記錄線程(正在執行I/O)。
http://www.torjo.com/log2/doc/html/workflow.html#workflow_2b
我覺得你的說法是錯誤的:使用互斥是沒有必要相當於一個同步的解決方案。是的,Mutex是用於同步控制,但它可以用於許多不同的事情。我們可以在例如生產者消費者隊列中使用互斥鎖,而日誌記錄仍然是異步發生的。
老實說,我沒有看過這些日誌記錄庫的實現,但它應該是可行的,使一個異步appender(對於log4j像lib)該記錄器寫入生產者消費者隊列和另一個工作線程負責寫入一個文件(甚至委託給另一個appender),以防它沒有提供。
編輯: 只是曾在log4cxx短暫的掃描,但它確實提供做什麼,我建議一個AsyncAppender:緩衝傳入記錄事件,並委託給連接的appender異步。
+1,用於突出顯示在推送指針/引用所需的短時間內鎖定隊列與鎖定整個磁盤寫入操作之間巨大的性能差異。 –
無鎖算法不一定是最快的算法。定義你的界限。有多少個線程用於記錄?最多隻能在單個日誌操作中寫入多少?
由於阻塞/喚醒線程,I/O綁定操作比線程上下文切換慢得多。使用10個寫入線程的無鎖定/旋轉鎖定算法會給CPU帶來沉重的負擔。
很快,在寫入文件時會阻止其他線程。
當您將日誌條目推入隊列時,請立即阻止其他線程 - 甚至更好。 –
如果您在寫入日誌文件時未使用互斥鎖,則可能因爲您的寫入訪問同時發生而導致崩潰或混合日誌 –