如何同步对非常频繁的读取/非常罕见的写入的全局变量的访问?

发布于 2024-12-07 11:17:13 字数 1198 浏览 0 评论 0原文

我正在研究服务器应用程序的调试日志记录基础设施。源代码中的每个日志记录点在其他参数中指定其级别(CRITICAL、ERROR 等)。 因此,在源代码中,日志记录点看起来像:

DBG_LOG_HIGH( … )

这是一个扩展为的宏

if ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) {
   // prepare and emit log record
}

,其中 DEBUG_LOG_LEVEL_HIGH 是预定义常量(假设为 2),而 CURRENT_DEBUG_LOG_LEVEL 是计算结果的某个表达式用户设置的当前调试日志记录级别。 最简单的方法是将 CURRENT_DEBUG_LOG_LEVEL 定义为:

extern int g_current_debug_log_level;
#define CURRENT_DEBUG_LOG_LEVEL (g_current_debug_log_level)

我希望允许用户在应用程序执行期间更改当前的调试日志记录级别,并且更改需要几秒钟才能生效。该应用程序是多线程的,对 g_current_debug_log_level 的更改可以轻松序列化(例如通过 CRITICAL_SECTION),但为了不影响性能表达式 ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) 应该尽可能快地执行,所以我想避免在那里使用任何线程同步机制。

所以我的问题是:

  1. g_current_debug_log_level 读取中缺乏同步会导致读取错误的值吗?虽然它不应该影响应用程序的正确性,因为用户可能将当前调试日志记录级别设置为不正确的值,但它可能会影响应用程序性能,因为它可能会导致它在不可控的时间段内发出大量调试日志。

  2. 我的解决方案能否保证当前调试日志记录级别的更改将在可接受的时间(假设几秒钟)后到达所有线程?理想情况下,我希望级别更改操作是同步的,以便当用户收到级别更改操作的确认时,她可以指望根据新级别发出后续日志。

我也非常感谢任何关于满足上述要求的替代实现的建议(对级别比较和同步级别更改的性能影响最小,延迟不超过几秒)。

I’m working on debug logging infrastructure for a server application. Each logging point in source code specifies its level (CRITICAL, ERROR, etc.) among other parameters.
So in source code logging point looks as:

DBG_LOG_HIGH( … )

which is a macro that expands to

if ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) {
   // prepare and emit log record
}

where DEBUG_LOG_LEVEL_HIGH is a predefined constant (let’s say 2) and CURRENT_DEBUG_LOG_LEVEL is some expression that evaluates to the current debug logging level set by the user.
The simplest approach would be to define CURRENT_DEBUG_LOG_LEVEL as:

extern int g_current_debug_log_level;
#define CURRENT_DEBUG_LOG_LEVEL (g_current_debug_log_level)

I would like to allow user to change the current debug logging level during the application execution and its okay for the change to take a few seconds to take effect. The application is multi-threaded and changes to g_current_debug_log_level can be easily serialized (for instance by CRITICAL_SECTION) but in order not to impact performance expression ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) should execute as fast as possible so I would like to avoid using any thread synchronization mechanism there.

So my questions are:

  1. Can the absence of synchronization in g_current_debug_log_level reads cause incorrect value to be read? While it should not affect application correctness because user could have set the current debug logging level to the incorrect value anyway it might affect the application performance because it might cause it to emit very high volume of debug log for uncontrollable period of time.

  2. Will my solution guarantee that change in the current debug logging level will reach all the threads after the acceptable amount of time (let’s say a few seconds)? Ideally I would like level change operation to be synchronous so that when user receives acknowledgement on level change operation she can count on subsequent log to be emitted according the new level.

I would also greatly appreciate any suggestions for alternative implementations that satisfies the above requirements (minimal performance impact for level comparison and synchronous level change with no more than a few seconds latency).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

萌逼全场 2024-12-14 11:17:14

在 x86 和 x64 上,易失性只会带来很少的直接成本。强制重新获取不相关的变量可能会产生一些间接成本(对易失性变量的访问被视为所有其他“地址获取”变量的编译器级内存栅栏)。将易失性变量视为函数调用,因为编译器将丢失有关调用过程中内存状态的信息。

在 Itanium 上,易失性有一定的成本,但也不算太糟糕。在 ARM 上,MSVC 编译器默认不为 易失性提供屏障(并且不提供排序)。

一件重要的事情是,您的程序中应该至少有一次对此日志级别变量的访问,否则它可能会变成常量并被优化掉。如果您打算通过调试器以外的任何机制来设置变量,这可能会出现问题。

On x86 and x64 volatile will impose very few direct costs. There may be some indirect costs related to forcing re-fetch of unrelated variables (accesses to volatile variables are treated as compiler-level memory fences for all other 'address taken' variables). Think of a volatile variable as like a function call in that the compiler will lose information about the state of memory across the call.

On Itanium, volatile has some cost but it's not too bad. On ARM, the MSVC compiler defaults to not providing barriers (and not providing ordering) for volatile.

One important thing is that there should be at least one access to this log level variable in your program, otherwise it might be turned into a constant and optimized out. This could be a problem if you were intending to set the variable through no mechanism other than the debugger.

暮色兮凉城 2024-12-14 11:17:14

定义在范围内可见的序数变量并根据需要更新它(当日志级别更改时)
如果数据正确对齐(即默认对齐),那么除了将当前日志变量声明为“易失性”之外,您不需要任何特殊的东西。这适用于 LONG 大小(32 位序数)。
所以你的解决方案是:

extern volatile long g_globalLogLevel;

不需要外部同步(即RWlock/CriticalSection/Spin等)

Define ordinal variable visible in the scope and update it as appropriate (when the log level changes)
If the data is correctly aligned (i.e. default one), then you don't need anything special except declaring your current log variable a "volatile". This would work for LONG size (32 bit ordinal).
So your solution would be:

extern volatile long g_globalLogLevel;

No need for external synchronization (i.e. RWlock/CriticalSection/Spin etc)

紫瑟鸿黎 2024-12-14 11:17:13

不存在任何要求在一个核心上的一个线程上进行的写入对另一个核心上的另一个线程读取可见的情况,而不提供某种栅栏来在写入和读取之间创建“先于”边缘。

因此,为了严格正确,您需要在写入日志级别之后和每次读取之前插入适当的内存栅栏/屏障指令。栅栏操作并不便宜,但它们比完整的互斥体便宜。

但在实践中,给定一个在其他地方使用锁定的并发应用程序,并且如果写入不可见,您的程序将继续或多或少地正确运行,那么写入很可能会由于其他原因而偶然变得可见。在短时间内完成围栏操作并满足您的要求。因此,您可能只需编写它并跳过栅栏即可逃脱惩罚。

但是使用适当的隔离来强制在边缘之前发生确实是正确的答案。 FWIW,C++11 提供了一个显式内存模型,它定义了语义并在语言级别公开了这些类型的防护操作。但据我所知,还没有编译器实现新的内存模型。因此,对于 C/C++,您需要使用库中的锁或显式防护。

There is nothing that requires that a write made on one thread on one core will ever become visible to another thread reading on another core, without providing some sort of fence to create a 'happens before' edge between the write and the read.

So to be strictly correct, you would need to insert the appropriate memory fence / barrier instructions after the write to the log level, and before each read. Fence operations aren't cheap, but they are cheaper than a full blown mutex.

In practice though, given a concurrent application that is using locking elsewhere, and the given fact that your program will continue to operate more or less correctly if the write does not become visible, it is likely that the write will become visible incidentally due to other fencing operations within a short timescale and meet your requirements. So you can probably get away with just writing it and skipping the fences.

But using proper fencing to enforce the happens before edge is really the correct answer. FWIW, C++11 provides an explicit memory model which defines the semantics and exposes these sorts of fencing operations at the language level. But as far as I know no compiler yet implements the new memory model. So for C/C++ you need use lock from a library or explicit fencing.

小傻瓜 2024-12-14 11:17:13

假设您使用的是 Windows,并且 Windows 仅在 x86 上运行(目前这基本上是正确的,但可能会发生变化...),并且假设只有一个线程写入该变量,那么您无需进行任何同步即可摆脱困境。

为了“正确”,您应该使用某种形式的读写锁。

Assuming you're on Windows and Windows only runs on x86 (which is mostly-true for now but may change...), and assuming only one thread ever writes to the variable, you can get away without doing any synchronization whatsoever.

To be "correct", you should be using a reader-writer lock of some form.

难忘№最初的完美 2024-12-14 11:17:13

鉴于您当前的实现,我建议您看一下原子操作。如果这仅适用于 Windows,请查看 互锁变量访问

Given your current implementation, I suggest you take a look at atomic operations. If this is intended for Windows only, look at Interlocked Variable Access

耳钉梦 2024-12-14 11:17:13

查看 Vista 和 7 上提供的新 Slim Reader/Writer 锁。它们应该以尽可能少的开销完成您想要的操作:

http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

Look at the new Slim Reader/Writer locks available on Vista and 7. They should do what you want with as little overhead as possible:

http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文