Thanx!!!
Why close the file in the function if the function is a possible
performance bottleneck?
DS
Bad idea.
> Question: if the execution switch from a thread to another when the
> file has not been closed yet, what can be written?
That depends on your open mode. But whatever happens it is very likely
not that what you want, because appending to a file is not atomic in
most cases and operating systems.
> Maybe I'll read something like two merged text?
Maybe.
> I should use the addlog() in many points of the threads functions, so
> a down() or up() in the addlog() could make slow the entire execution,
> or am I wrong?
Whatever up() and down() is.
> How can I log in the same file without problems?
Either use a mutex or use the same stream handle for all threads and
write with a single runtime call. normally calls to the i/o functions of
a single stream are serialized by the runtime anyway.
> I also could write more logs, one-per-thread, but find a bug reading
> more files will be hard...
Yes.
In fact you should always write
- a time stamp,
- the current thread id and
- the stack pointer
in addlog. Otherwise it will be difficult to interpret the log.
If not otherwise needed you may simply log to stderr which you can
redirect to a file when starting your application.
Marcel
I did a logging system that went something like this:
1. a dedicated logging thread that was comprised of a list of registered
application threads.
2. a plurality of application threads that have a private single
producer/consumer queue per-thread.
3. a log entry comprised of a timestamp, thread id, importance, status,
ect...
4. log entries are enqueued by a thread into its private queue.
5. dedicated logging thread periodically samples each of registered
application threads private queues.
6. log entries are dequeued, sorted, and written to a file by the single
dedicated log thread.
This logging scheme scales, and does not even need any mutexs on the single
producer/consumer queues.
It worked very well.
Chris M. Thomasson wrote:
[...]
> This logging scheme scales, and does not even need any mutexs on the
> single producer/consumer queues.
not even for the allocation of storage for the data that is logged?
I wonder a bit who owns the storage while the log items are in the queue.
Another disadvantage might be that the last log messages are lost in
case of an application crash. OK, a signal handler might recover from this.
Marcel
It really depends on the implementation of the underlying allocator. In the
particular scheme I created, log messages were finite in length, so a thread
pre-allocated them in bulk at birth; the act of dequeuing a pre-allocated
buffer is very cheap.
> I wonder a bit who owns the storage while the log items are in the queue.
Once a log buffer was queued, it belongs to the dedicated logger thread; it
was a simple transfer of ownership. The logging thread was responsible for
freeing buffers. Also, I simply failed to mention that when a thread ended
its lifetime, its TSD dtor fired up which transferred all remaining log
entries to the main log thread via a multiple producer single consumer
queue.
> Another disadvantage might be that the last log messages are lost in case
> of an application crash.
If an application crashes, well, AFAICT its undefined exactly where things
stop working. So, IMVHO, an application crash can adversely effect other
logging schemes as well.
> OK, a signal handler might recover from this.
Indeed.
One may use following scheme to manage memory (atomic-free too):
http://groups.google.com/group/comp.programming.threads/browse_frm/thread/378a35b21ae2b42e/
Although it requires per-producer buffering of memory, so it may cause
excessive memory consumption. So one better bound number of per-
producer memory blocks, and use something like spin-wait if all memory
blocks are busy. Otherwise one can run into following very bad
situation:
http://blogtrader.net/page/dcaoyuan/entry/a_case_study_of_scalable
> I wonder a bit who owns the storage while the log items are in the queue.
Consumer (log thread).
> Another disadvantage might be that the last log messages are lost in
> case of an application crash. OK, a signal handler might recover from this.
Another solution is to allocate log buffers in shared memory.
--
Dmitriy V'jukov
> This logging scheme scales, and does not even need any mutexs on the single
> producer/consumer queues.
I am only a bit concerned with the fact that large amount amounts of
memory are constantly transferred between the threads. So this may
cause a large cache-coherence overheads, which may be significantly
larger than mutex acquisition. What do you think about this? Or logger
thread is not touching the memory and just simply offloads it to the
OS for writing to the file?
--
Dmitriy V'jukov
> > This logging scheme scales, and does not even need any mutexs on the
> > single
> > producer/consumer queues.
> I am only a bit concerned with the fact that large amount amounts of
> memory are constantly transferred between the threads. So this may
> cause a large cache-coherence overheads, which may be significantly
> larger than mutex acquisition. What do you think about this?
I did not consider cache-coherence overheads in this case. It was a long
time ago. I cannot remember results of performance testing wrt per-thread
queue vs. global queue. IIRC, log entries were frequent in some cases; I did
not want to take a lock for each one.
> Or logger
> thread is not touching the memory and just simply offloads it to the
> OS for writing to the file?
The logger thread did do some sorting on log entry timestamps and thread id;
write to file; free log entry... IIRC, it only mutated the embedded double
linked list nodes during the sorting phase.
> One may use following scheme to manage memory (atomic-free too):
> http://groups.google.com/group/comp.programming.threads/browse_frm/thread/378a35b21ae2b42e/
> Although it requires per-producer buffering of memory, so it may cause
> excessive memory consumption. So one better bound number of per-
> producer memory blocks, and use something like spin-wait if all memory
> blocks are busy. Otherwise one can run into following very bad
> situation:
> http://blogtrader.net/page/dcaoyuan/entry/a_case_study_of_scalable
IIRC, the logging scheme I created could run into similar scenario.
Although, it never was a problem wrt the applications which used it.
However, I think it could be solved by using a per-thread fast-pathed
semaphore, or a single global semaphore.
Something like:
struct log_entry {
struct log_entry* next;
struct log_entry* prev;
// [...]
};
struct thread {
dlist link;
semaphore log_max; // initial value to 1000, or something.
struct log_entry* log_freelist;
spsc_queue log_queue;
};
struct log_thread {
dlist threads;
event ec;
};
static struct log_thread g_logger;
void thread_log(struct thread* self, ...) {
if (! self->log_max.try_wait()) {
g_logger.ec.signal();
self->log_max.wait();
}
// enqueue
}
void log_thread() {
for (;;) {
g_logger.ec.timed_wait(poll_interval);
for_each_thread(struct thread* thread) {
unsigned count = 0;
for_each_log_entry_in_thread(struct log_entry* log) {
// [...];
++count;
}
thread->log_max.post(count);
}
}
}
> [...]