我正在研究多线程C ++代码,它可以作为exe或XLL Excel加载项运行,在任何一种情况下,它都将在多个线程中运行。
记录日志的好习惯是什么?
C#程序Logging in multithreading environment and testing上有一个类似的主题,暗示:
将您的日志记录实例包装在一个线程安全的单例中。不要用 双重检查锁定!此外,使用日志记录可能也是有意义的 像log4net或Enterprise Library 5这样的库。
我想知道这是否适用于C ++程序?
有人说Singleton is evil,但似乎loggins是这种模式的有效案例?
哪些好的日志记录库会处理流缓冲区问题,因此通过日志文件可以信任地重建场景?
使用Singleton从多个线程进行日志记录会降低性能吗?是否必须使用互斥锁?
我正在开发Windows环境。
答案 0 :(得分:1)
如何在不使用Singleton的情况下从多线程进行日志记录?
我发现这个问题很奇怪。
访问基于ram的日志(循环式)的关键部分是:
我经常在某个类实例的构造函数中创建日志,这对于正在发生的事情至关重要。创建后,任何线程都可以通过名称访问日志。在以下情况中," M_dtbLog"。
if(0 == M_dtbLog) { M_dtbLog = new DTB :: Log(1 * 60 * 1000,1024 * 256 * 2); dtbAssert(0!= M_dtbLog); }
update 10/06 10:45 - 忘了提到这个dtblog是在2011年之前创建的,所以没有std :: thread。线程是Posix线程。
1)拉入代码
#include "../../bag/src/dtb_log.hh"
2)那时候,为方便起见,我添加了一个宏(如果必须的话)。 例如:
// several years ago (in a much more c-style approach),
#define DBG if(M_dtbLog) (void)M_dtbLog->enq
2b)那时我还补充道:
// the compiler checks many more things for printf does
// use this simple #define temporarily to test your DBG's
#if (0)
#undef DBG
#define DBG if(M_dtbLog) (void)::printf
#endif
3)" DBG"因为代码注入器现在可用,并且每次调用都确认该代码可用。例如:
void LMBM::Node:init(void)
{
if(m_next)
m_next->init();
//...
DBG("m_semIn %2d = %p\n", m_nodeId, m_semIn); // <<<<<<<<
更新10/06 10:45 - 忘了提到这些Node每个都有自己的线程和输入信号量,因此静态M_dtbLog。默认设置创建10个节点(以及10个线程和信号量)。这些节点中的每一个都可以直接访问静态M_dtbLog。
4)最近,我使用了我认为更简单的C ++方法......在其他时间进行讨论。
5)有时候更简单更好,特别是对于单元测试。在这里,我有时使用std :: stringstream(这不是线程安全的)。只需要记住在任务结束时限制进入代码的程度。
6)对于生产,当不需要Log时,如果不需要(通常不是),我根本不会实例化日志。但是我将代码中的DBG语句留在需要调试的时候。
在您要使用此日志的任何文件/类中,无论使用哪个线程,只需添加#include,以及#define和您想要的多个DBG。
目前无法支持衡量效果问题。它写给ram,为了我的目的足够快。
独占访问控制(用于多线程访问)在dtb_log实现中。
在我的Linux版本的代码中,我在本地模式下使用Posix Process Semaphore(即不处理共享,不是互斥或std :: mutex)。
构建字符串并预先挂起时间和线程ID等的一般工作是在自动内存(通常称为堆栈)中完成的,这样线程(每个都有自己的堆栈)不能相互干扰。
然后将自动内存中的字符串排入循环内存缓冲区。 (使用私有方法DTB :: Log :: enqRrq())任何线程都可以使用dtb_log。临界区以典型方式使用。 &#39; enqRrq&#39;约12行代码,只需将角色从一个buff改为循环buff。
更新10/6 - 代码段 -
按照代码注入时间戳,线程ID和消息等
// buff 1 now ready to install
enqRrq(buff1, retVal); // install text into round-robin queue
接下来,将新日志条目传输到循环缓冲区
void DTB::Log::enqRrq(const char* buff, size_t sz)
{
DTB::ProcessSemCritSect critSect(m_mutex);
dtbAssert(0 == m_rrBuff[m_nextIn]); // code broken
// move char from buff into m_rrBuff
{
{
bmpNextIn(); // leave one null between records
for (size_t i = 0; i < sz; i += 1) // insert sz chars of buff
{
m_rrBuff[m_nextIn] = buff[i];
bmpNextIn(); // m_rrBuff is round-robin
}
m_rrBuff[m_nextIn] = 0; // insert null char at end of m_rrBuff
}
}
uint32_t lni = m_nextIn + 1; // lni starts after null we just inserted
if (lni >= m_rrBuffSize) lni = 0; // round-robin
// stomp 0's to end of oldest message
{
while (0 != m_rrBuff[lni]) // while in middle of oldest message
{
m_rrBuff[lni++] = 0; // stomp on oldest message (to its end)
if (lni >= m_rrBuffSize) lni = 0; // round-robin
}
}
}
我碰巧找到了这种日志输出的几个例子。对不起线长,最大的是160个字符。
请注意,这是我找到的日志的最后5秒。
retrieve_shelf_inventory()显然是每秒一次。 (通常是重复的日志条目在被用户环绕并踩到你想要看到的内容之后被抑制,
最后一秒显示了对许多LED和办公室警报的更新。
pem_cm,一个不同的代码层,[一个线程块],正在发送触发这些日志条目的命令。
此系统有大约1000个线程在运行 还有100个左右的开发人员在编写代码。
05:27:41 0x010117 262.816 mcu_t :: retrieve_shelf_inventory()size = 91 05:27:41 0x010117 262.816 --mcu:sdd_io2(ant = 210(SDD_GET_SHELF_PHY_INV)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,519,(1109792 us)
05:27:42 0x010117 263.928 mcu_t :: retrieve_shelf_inventory()size = 91 05:27:42 0x010117 263.928 --mcu:sdd_io2(ant = 210(SDD_GET_SHELF_PHY_INV)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,520,(1111872 us)
05:27:43 0x010117 265.040 mcu_t :: retrieve_shelf_inventory()size = 91 05:27:43 0x010117 265.040 --mcu:sdd_io2(ant = 210(SDD_GET_SHELF_PHY_INV)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,521,(1111873 us)
05:27:44 0x010117 266.152 mcu_t :: retrieve_shelf_inventory()size = 91 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 210(SDD_GET_SHELF_PHY_INV)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,522,(1111883 us)
05:27:44 0x010117 266.152 mcu_t :: sdd_control_led(indx = 13,color = 1,action = 1)= 0 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,523,(48 us)
05:27:44 0x010117 266.152 mcu_t :: sdd_control_led(indx = 14,color = 1,action = 1)= 0 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,524,(19 us)
05:27:44 0x010117 266.152 mcu_t :: sdd_control_led(indx = 15,color = 2,action = 1)= 0 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,525,(19 us)
05:27:44 0x010117 266.152 mcu_t :: sdd_control_led(indx = 12,color = 0,action = 0)= 0 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,526,(21 us)
05:27:44 0x010117 266.152 pci2:blsr:office_alarms:change()aud_CR.vis_CR.aud_MJ.vis_MJ.aud_MN.vis_MN 0000003f 05:27:44 0x010117 266.152 --mcu:sdd_io2(ant = 35(SDD_SET_ALARM)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,527,(26 us)
05:27:44 0x010117 266.200 mcu_t :: sdd_control_led(indx = 13,color = 1,action = 1)= 0 05:27:44 0x010117 266.200 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,528,(49 us)
05:27:44 0x010117 266.200 mcu_t :: sdd_control_led(indx = 14,color = 1,action = 1)= 0 05:27:44 0x010117 266.200 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,529,(20 us)
05:27:44 0x010117 266.200 mcu_t :: sdd_control_led(indx = 15,color = 2,action = 0)= 0 05:27:44 0x010117 266.200 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,530,(19 us)
05:27:44 0x010117 266.200 mcu_t :: sdd_control_led(indx = 12,color = 0,action = 0)= 0 05:27:44 0x010117 266.200 --mcu:sdd_io2(ant = 20(SDD_LED_CONTROL)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,531,(20 us)
05:27:44 0x010117 266.200 pci2:blsr:office_alarms:change()aud_CR.vis_CR.aud_MJ.vis_MJ.aud_mn.vis_mn 0000001b 05:27:44 0x010117 266.200 --mcu:sdd_io2(ant = 35(SDD_SET_ALARM)sender = 0001020e(pem_cm),sec_id = 4088C04080000000)eqType = 35 major = 1,532,(25 us)
513行,55287字节(m_size = 65536) show,55287
更新11/04
有人说单身人士是邪恶的,但似乎loggins是一个有效的案例 这种模式?
我使用了几个单身人士,但最终发现单身人士没有增加任何价值。它确实有一个令人难忘的名字,所以它更容易记住,所以也许对其他人更具可读性,所以你看到的比其他模式更多(我认为)。但是,即使我使用这些想法来访问和控制只有一个......这个单身人士并没有简化或加速任何事情。
我还没有看到单身人士所提供的代码&#39;线程安全。我相信模式书(发布模式)有一个声明说,基本上,没有一个模式是线程安全的。 (但是从我看起来已经有一段时间了。)也许C#单身人士会有所不同。
当你引用的作者说,&#34;将你的日志记录实例包裹在一个线程安全的单例中。&#34;,我相信他建议不超过a)使用单例,并且b)添加线程对单身人士的安全。单例不提供线程安全性,线程安全性不提供单例功能。
更新11/04
使用Singleton从多个线程进行日志记录 降低性能?是否必须使用互斥锁?
正如我上面所说,我没有看到单例提供线程安全的想法。 imho它没有。
性能总是受到访问共享资源的多个线程同步的影响。上面的代码使用POSIX信号量创建一个临界区。该实现看起来像:
DTB::ProcessSemCritSect::ProcessSemCritSect(ProcessSemaphore* a_processSem) :
m_processSem (a_processSem)
{
assert(nullptr != m_processSem);
int wait_status = m_processSem->wait(); // block if already locked
assert(DTB::ProcessSemaphore::SUCCESS == wait_status);
}
DTB::ProcessSemCritSect::~ProcessSemCritSect(void)
{
assert(nullptr != m_processSem);
int post_status = m_processSem->post(); // un-block any waiting thread
assert(DTB::ProcessSemaphore::SUCCESS == post_status);
m_processSem = 0;
}
我的基准测试显示,在我的系统上,两步wait()和post()非常快。 25 ns。
如果尝试使用日志的线程具有最小的冲突,那么这将是信号量开销的下限(在我的系统上)。
在这种关键部分中碰撞的线程(可能在不同的核心中)可能需要12,000 ns才能在我的系统上进行上下文切换。你的表现会有所不同。