0

I'm making a logger. I want to create a function log() that takes a stream as input.

For instance:

log("hello"<<" "<<"world"<<10<<"\n"); 

I also want it to be thread safe.

I've redefined the << operator so I can do:

log()<<"hello"<<"world"<<10<<"\n" 

But this operation is not thread safe.

How can I make it thread safe?

3
  • Without seeing more of your code, I'm not sure we can help you. Commented Feb 15, 2012 at 16:24
  • 1
    Oh, you just pass the --make-my-code-threadsafe option to your compiler. Commented Feb 15, 2012 at 16:32
  • Do you maybe mean "how to make that method chaining atomic/transactional"? Commented Feb 15, 2012 at 16:39

3 Answers 3

9

Have log() return a temporary object that buffers all output in memory. The destructor for this object will run at the end of the expression, and should flush the accumulated data to the actual stream in a single atomic operation (up to you to make that operation atomic).

That will make your second syntax feasible:

log()<<"hello"<<"world"<<10<<"\n"; 
Sign up to request clarification or add additional context in comments.

11 Comments

Note that to return a temporary object, the type must be copyable. Unless you have move semantics, you need to simulate them somehow, so that only the final copy does the flush. (You also want to take precautions to ensure that no exceptions escape from the destructor---it's more than conceivable that flushing a buffer could raise an exception.)
@James: Or just make the destructor for the copied-from object note that its buffer is still empty, and skip the flush. Besides that, log() could itself be a constructor call, then there are no copies required. The point about exceptions during flush is much more important.
log() could be a constructor call, but it's a lot more flexible if it is a function. (At least, this has been my experience. But my log() functions have generally done a lot more, inserting headers into the output, with timestamps, and so on. Nothing that can't be done in a constructor, but sometimes it's more convenient to use the function. And move semantics aren't too difficult to simulate, at least for what they're used for here.)
@James: Is the copy made during return formally inside the function? In that case, there can be a private "copy" constructor (which may have move semantics) to which access is granted via friend. And copies elsewhere are still prevented.
The temporary could be worked around if log() returned a type that was essentially shared_ptr<BufferType> with operator<< overloads (that just forward to corresponding BufferType methods). The final flush occur in the BufferType destructor.
|
1

You can't create a function like the one you want. You can, however, create a macro that handles that stuff for you:

// "log" may be defined in the <cmath> header file // so undefine it if needed #ifdef log # undef log #endif #define log(stream) \ do { \ acquire_lock(); \ std::cout << stream; \ release_lock(); \ } while(0) 

You have to change the acquire_lock and release_lock calls to the proper ones for you. And of course use a stream that is appropriate for you as well.

10 Comments

why use #define when there is a beautiful thing called class?
@Default: Because you need textual substitution to get the left-to-right binding between the stream and the arguments, in the first example.
-0.3: You can create such function with a transaction proxy (I've done this myself to multiplex logging to several streams, some for colorful live output, some for file output, etc.). -0.3 for the hiding macro. -0.6 in summary. Makes no full downvote.
#undefing log is not a good idea. If you need a macro (which you don't, but you may want it for other things, like inserting __FILE__ and __LINE__, don't call it log. Use something else, like LOG.
@phresnel: There's no way to make the first snippet work without a macro, because "hello" << " " is undefined.
|
0

in C++03 all operations are not thread safe

6 Comments

In C++03, the compiler never provides thread safety for you. That doesn't mean that you can't use implementation-provided synchronization APIs to get thread safety.
In standard C++03, nothing is thread safe (and I know of at least one compiler which used static variables when stack unwinding an exception---which lead to some interesting results when two threads threw an exception simultaneously). Many compilers do offer additional guarantees, however---a compiler can't be Posix compliant unless it does, for example.
@BenVoigt If the compiler doesn't guarantee some degree of thread safety, then you can't use it in a multithreaded environment. See my previous comment about the use of static variables during stack unwinding.
@James: That still only affects "magic" behavior added behind the scenes, which isn't hard to avoid if you restrict yourself to an appropriate subset of C++. It is a systems-programming language after all.
@BenVoigt The problem is knowing what the appropriate subset is. Conceivable, a compiler could store the return address of a function in a static variable before pushing it onto the stack. (I've actually used a Pascal compiler which did this, many, many years ago.)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.