开发者

How to synchronize access to a global variable with very frequent reads / very rare writes?

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 eff开发者_JAVA百科ect. 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).


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.


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.


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


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


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.


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)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜