CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 2 12 LastLast
Results 1 to 15 of 17
  1. #1
    Join Date
    Apr 2008
    Posts
    18

    Unhappy Using Multitreading to log

    I am creating a logging .dll utility. Which shud support multithreading.
    I am a bit confused about how shud I be handling multithreading using MUTEX.

    From the application(using my dll) I can call initializelog() function which get's the necessary data from properties file etc, and logmin(),logmid() and logmax() functions which passes the messages to the dll to log. Now if I have many threads running in my application and each thread calls any of the above functions. It should pass the message and the thread ID to dll to log. I know the Mutex function which synhronises the threads. But am confused WERE and HOW to use this in my DLL to synchronise the threads one at a time.

    Please help me out to solve this..
    THANKS

  2. #2
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,396

    Re: Using Multitreading to log

    I don't see any need in Mutex nor any other synchronize object for your logging.
    Just PostMessage a user defined message form a thread to the dll (dll must has a window to handle messages - if it doesn't have yet, then you could create an invisible window in it).
    See this essay with code examples of how to do it: Worker Threads (particularly the section "Worker threads and the GUI II: Don't touch the GUI")
    Victor Nijegorodov

  3. #3
    Join Date
    Apr 2008
    Posts
    18

    Re: Using Multitreading to log

    I am trying to created something like the log4j(JAVA's) logging dll in C++. SO I would have to synchronise the threads and print the messages accordingly if multiple threads are running....

  4. #4
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,396

    Re: Using Multitreading to log

    Again: there is nothing to synchronise (at least, your problem description doesn't point out that there might be something requiring any synchronisation).
    Each thread should just post messages with the log data to your dll and dll will consecutively log this data.
    Victor Nijegorodov

  5. #5
    Join Date
    Feb 2002
    Posts
    4,640

    Re: Using Multitreading to log

    Just to add to Victor's statements, by posting messages to your logger, you're effectively serializing all access to your logger. So, there is nothing to synchronize, since there is only one thread accessing your logging code.

    Viggy

  6. #6
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Using Multitreading to log

    If you have a multithreaded app, you generally expect each thread to operate independently of the other. Well, for best performance anyway, this is desirable.

    If you add logging support as you've described you've essentially serialized any calls to the logger and you may have adversely effected your performance.

    So IMO, if you need to add logging operations, a good idea is to add logging without serializing the calls (or at the least perform the minimum amount of serialization).

    One way to do this is to use a shared std::queue and an event using a producer/consumer model.

    Create a thread for the consumer that's whole purpose in life is to sit around waiting for a queue event. When it receive an event, it checks the queue and pops off the first item, saves it to a file, and then checks for another queue item. If no queue item is found the thread goes back to sleep until the next queue event is set.

    The threads that are logging (i.e. the producer threads), just push an item onto the queue and set the queue event.

    Using this technique, there isn't any need to have a hidden window or involve the Windows messaging system. (For those of you familiar with my posts know that I prefer not to pass data around via Windows messages - it can be unreliable because of the fact that we can't be sure of what user actions are going to be done to the UI. I've seen cases in poor designs where the user just moving the mouse back and forth have caused data loss from a piece of hardware).

    Now, anytime a resource is shared between thread for reading and writing resource access needs to be synchronized, and this case is no different. However, the goal is to minimize the amount of time a thread has to wait on acquiring a lock for the resource. That's why we have the queue to begin with, if each thread just wrote to the log file, each thread would have to wait for another thread to finish writing before it was able to write.

    If we use a queue, a thread only has to wait for the time to push an item onto the queue before it's able to go on about its work. Many times, with logging you'll want to pass a log structure of data rather than just a simple message. You may want to record a severity, thread id, and/or some other data along with the message.

    While we could create a std::queue like ( std::queue<LOG>), it would be better if our queue contained pointers to the log items (i.e. std::queue<LOG*>). That way only have to lock the queue for as long as it takes to push the pointer to the item onto the queue, rather than taking the time hit for a log object to be copy constructed into the queue.

    You can encapsulate this very nicely into a dll. You provide Logging start and stop functions that start and stop the queue service thread and also provide a few functions that allow you to log. You may have a couple of predefined functions that take a couple of params using defaults (such as type Debug and a message) or one that takes the log structure. The important thing is the encapsulation so that the users of the log methods need not know that you using a queue and a separate thread to write to the log file.

    This type of approach can really minimize the time your individual threads are spent 'doing logging'.

    For a sample of this, check out the StartStop.zip file in this post. This thread uses a shared queue to simulate log data being passed from a worker thread to a UI thread. However, because the queue servicing thread is the UI thread and the log items are put into a list box (rather than a log file), this sample doesn't use an event but uses a user defined message to signal the UI thread that a log item is in the queue.

    Just so you don't think I contradicted myself with regard to preferring not to use windows messages - in this case, it wouldn't make sense to spin off another thread to service the log queue, because that thread wouldn't be able to insert the log items into the listbox. Besides the user defined message is only used to signal the UI thread that a log item is available in the queue - it doesn't pass any log data.

    For an example of logging from one application into another app where the second application is the consumer thread that just writes the log items to the log file, check out the LogSnd/LogRcv projects in the Win32 Thread Synchronization articles in my subject line. These two samples use a memory mapped file to pass the log data between the two processes (and they still use queues to buffer the data).
    Last edited by Arjay; May 19th, 2008 at 04:54 PM.

  7. #7
    Join Date
    Nov 2003
    Posts
    1,902

    Re: Using Multitreading to log

    >> If you add logging support as you've described you've essentially serialized any calls to the logger ...
    So you're saying that by calling PostThreadMessage(), by possibly multiple threads, the flow of data into the logger thread is "serialized". I can agree with that - although the order in which messages are received by the logger thread isn't "well defined". In other words, if two threads call PostThreadMessage() "at the same time", then the order in which the messages are removed is not "well-defined". But that probably doesn't matter for a logger. To me the term "serialized" implies order, where "synchronized" implies "correct MT access to shared data".

    >> I prefer not to [use Windows] messages - it can be unreliable because of the fact that we can't be sure of what user actions are going to be done to the UI.
    I don't quite understand what "user actions" have to do with reliability. As far as I understand, a successful call PostThreadMessage() will result in that message being delivered reliably.

    >> When [the consumer] receives an event, it checks the [std::]queue ... If no [std::]queue item is found the thread goes back to sleep...
    The only difference between this and using a windows message queue is that your are managing the actual queue instead of the OS.

    >> [If you use a windows message queue] ... you've essentially serialized any calls to the logger and you may have adversely effected your performance.
    There's probably little to no difference in performance between using a windows message queue or managing your own. They both operate on the same principle - a shared queue. One would need to benchmark the two approaches before claiming one is "faster" than the other. For something like a logger, it really just comes down to preference.

    >> While we could create a std::queue like (std::queue<LOG>), it would be better if our queue contained pointers to the log items (i.e. std::queue<LOG*>).
    I think this is more of a preference issue as well. Using pointers implies heap allocated memory. Now you are allocating from the heap along side whatever heap allocations are done by std::queue. If you use the "std::queue<LOG>" approach, then only the container is touching the heap and you don't have to bother with managing the objects lifetime yourself. You may also want to consider each approach based on where each LOG object is placed in memory - for fewer CPU cache line misses. (But that's "premature optimization" in my opinion. Follow the rules of K.I.S.S., get it working, then optimize )

    >> ... rather than taking the time hit for a log object to be copy constructed ...
    Since my preference is "std::queue<LOG>" instead of "std::queue<LOG*>" whenever possible - that's gotta be one expensive copy constructor to push me over to the "LOG*" way . For example, if the constructor calls new (which is relatively expensive), then moving the construction of the object outside the std::queue's mutex may be well worth it if there is high contention for that mutex.

    gg

  8. #8
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Using Multitreading to log

    >> I prefer not to [use Windows] messages - it can be unreliable because of the fact that we can't be sure of what user actions are going to be done to the UI.
    I don't quite understand what "user actions" have to do with reliability. As far as I understand, a successful call PostThreadMessage() will result in that message being delivered reliably.
    If the thread writing to the log file is the UI thread, user actions most certainly can get in the way. So if worker threads are sending log messages to the UI thread, they will be queued until the UI thread can respond to them. If the UI is busy processing other 'windowy' messages, this may be a problem.
    >> When [the consumer] receives an event, it checks the [std::]queue ... If no [std::]queue item is found the thread goes back to sleep...
    The only difference between this and using a windows message queue is that your are managing the actual queue instead of the OS.
    Exactly. I would rather maintain my own dedicated queue rather than sharing a Windows message queue. I keep my data separate from my UI processing. To me, messaging is for doing 'windowy' things only.

    When multithreading, my approach is that any resources that are shared between threads never are passed via messaging. The only time I use a message is to notify a UI thread that some 'event' has occurred (but again typically no data is passed in this message).

    >> [If you use a windows message queue] ... you've essentially serialized any calls to the logger and you may have adversely effected your performance.
    There's probably little to no difference in performance between using a windows message queue or managing your own. They both operate on the same principle - a shared queue. One would need to benchmark the two approaches before claiming one is "faster" than the other. For something like a logger, it really just comes down to preference.
    There is a difference if the UI thread is busy and the data transfer gets interrupted.

    Years ago, I rearchitected an application for a client that relied on messages to pass data from a thread servicing some hardware across a serial port. The previous application had a secondary thread servicing the serial port and used windows messaging to pass this data to the UI (to be processed further). The best baud rate that could be achieved was 19.2k and that was only if the user didn't touch the app during the data transfer. If the user wiggle the mouse during the transfer, data was lost.

    After the redesign (using a thread safe shared queue), the data transfer reliably could handle 115.2K and it didn't matter what the user was doing at the time. The client was amazed that we were operating at that rate, and was even more amazed when I reached down and moved the mouse around and it didn't impact the transfer.

    >> While we could create a std::queue like (std::queue<LOG>), it would be better if our queue contained pointers to the log items (i.e. std::queue<LOG*>).
    I think this is more of a preference issue as well. Using pointers implies heap allocated memory. Now you are allocating from the heap along side whatever heap allocations are done by std::queue. If you use the "std::queue<LOG>" approach, then only the container is touching the heap and you don't have to bother with managing the objects lifetime yourself. You may also want to consider each approach based on where each LOG object is placed in memory - for fewer CPU cache line misses. (But that's "premature optimization" in my opinion. Follow the rules of K.I.S.S., get it working, then optimize )
    As a mentioned, this functionality can be encapsulated within a component (such as the dll, the op mentioned). My preference (via perf testing) is to store pointers in the queue. It is simply more performant to only have to lock the queue to push, getat or pop a pointer from the queue versus operating on the actual object in the queue. Not only is the lock longer while pushing the item onto the queue, but the lock has to be held while processing the item when it's popped from the queue (i.e. the queue is locked during the expensive operation of writing to the log file).

    If you look at either one of the samples I referred to, you'll see that storing a pointer in the queue helps keep the lock times to an absolute minimum. The more threads that access the queue, the more important it is to keep queue locking to the minimum.

    >> ... rather than taking the time hit for a log object to be copy constructed ...
    Since my preference is "std::queue<LOG>" instead of "std::queue<LOG*>" whenever possible - that's gotta be one expensive copy constructor to push me over to the "LOG*" way . For example, if the constructor calls new (which is relatively expensive), then moving the construction of the object outside the std::queue's mutex may be well worth it if there is high contention for that mutex.
    If you are in-process and you are interested in performance, then you may want to look at using a critical section over a mutex.

    Of course, my preference for taking this approach is based on my testing and experience; others may prefer to do otherwise.
    Last edited by Arjay; May 19th, 2008 at 09:05 PM.

  9. #9
    Join Date
    Apr 2008
    Posts
    18

    Re: Using Multitreading to log

    And what If I have multiple threads accesing my code.

  10. #10
    Join Date
    Apr 2008
    Posts
    18

    Re: Using Multitreading to log

    THank you for your replies...
    The application,usind my dll, creates multiple threads and each thread can call functions logmin,logmin or logmax to log any no. of times. These functions are contained in my DLL file. I also have to pass the thread ID to dll to log it. I HAVE to use MUTEX, in dll, to synchronise these threads. Kindly please suggest me on this...thanks

  11. #11
    Join Date
    Aug 2002
    Location
    Cluj-Napoca,Romania
    Posts
    3,496

    Re: Using Multitreading to log

    Quote Originally Posted by pl_kode
    THank you for your replies...
    The application,usind my dll, creates multiple threads and each thread can call functions logmin,logmin or logmax to log any no. of times. These functions are contained in my DLL file. I also have to pass the thread ID to dll to log it. I HAVE to use MUTEX, in dll, to synchronise these threads. Kindly please suggest me on this...thanks
    Have you read all the suggestions posted above? You really do not need to synchronize your logging features.

    Anyways, if you really want to do a synchronization you'll end up slowing down your application and having threads waiting for access to the logging facilities.Is this something you really want to happen? Basically a secondary activity ( logging) will interfere with the main activity of your threads ( calculations).
    Har Har

  12. #12
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,396

  13. #13
    Join Date
    Apr 2008
    Posts
    18

    Re: Using Multitreading to log

    Nervermind of slowing down the application. But I have to use synchronisation by mutex. Please help me out on this.
    Till now.. I created an application in which three threads are being created and each thread is calling logmin,logmid and logmax functions by passing the thread ID to it. Now In my dll applicaion. I have not done anything to synchronise these threads. How and were shud I put the synchronisation which would help me synchronize the threads. At present my dll does simple logging by passing messages to it.

  14. #14
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,396

    Re: Using Multitreading to log

    You didn't explain yet why you "have to use synchronisation by mutex". Are you still sure you really have to?

    And, please, read all these essays I referred to in my previous post.
    Victor Nijegorodov

  15. #15
    Join Date
    Nov 2003
    Posts
    1,902

    Re: Using Multitreading to log

    >> If the thread writing to the log file is the UI thread...
    Ok - now I see where you were coming from.

    >> If you add logging support as you've described...
    I didn't realize anyone suggested that the OP should perform the log-file writing in the UI thread. That's the source of my confusion. I read all your comments in the context of a worker-thread using a windows message queue for communication.

    >> but the lock has to be held while processing the item when it's popped from the queue
    No it doesn't. Once it's popped out of the queue, you can release the lock. Why would you ever hold on to the lock while processing an element you've removed (popped) from the queue? The lock is for accessing the queue in a mutually exclusive manner. Once you are no longer accessing the queue, you can release the lock.

    >> It is simply more performant to only have to lock the queue to push, getat or pop a pointer
    This only affects "performance" if there is very high contention for the lock. With low contention on the lock, the overall performance will actually be worse, simply due to the overhead of calling new and delete. I'll back that up with benchmarks...

    >> ... using a critical section over a mutex
    That's my mistake for using "mutex" in the general sense of "mutually exclusive code" - I did not intend to mean "win32 mutex" (which is more expensive than a win32 critical section).

    >> ... others may prefer to do otherwise.
    Well, I prefer to use benchmarks - which follows:

    The attached code uses a server and client thread to test 3 types of inter-thread communication via Q's:
    1) "PayloadQ" - this is the "std::queue<Payload>" version
    2) "PayloadPtrQ" - this is the "std::queue<Payload*>" version
    3) "Win32Q" - this is the PostThreadMessage() version

    The code also allows for the configuration of the following properties of each test:
    1) Low and High lock contention
    2) Payload structure size (64 vs 512 bytes in my test runs)

    Results:
    Code:
    NUM_MSGS = 1,000,000
              Payload Size | Contention  Elapsed  |  Contention  Elapsed
    PayloadQ       64      |    Low        4286   |     High       2266
    PayloadPtrQ    64      |    Low        4880   |     High       1686
    Win32Q         64      |    Low       16531   |     High      17312
    -----------------------------------------------------------------------
    PayloadQ       512     |    Low        3164   |     High       3226
    PayloadPtrQ    512     |    Low        3588   |     High       2026
    Win32Q         512     |    Low       16766   |     High      17547
    As I pointed out earlier, PayloadQ outperforms PayloadPtrQ when the lock contention is low. When lock contention is high, PayloadPtrQ does better. As you can see, Windows message queue's have a lot of overhead associated with them (as Arjay discovered while re-architecting).
    These numbers should be put into perspective - the code is simply calling Enqueue and Dequeue as fast as it can (for the high contention runs). When you apply these Q strategies to real code, the performance difference between one Q strategy vs another can easily become negligible.

    >> if you really want to do a synchronization [manually] you'll end up slowing down your application
    As you can see, the manual approach to Q's and synchronization is more efficient than windows message queues.

    >> How and were shud I put the synchronisation which would help me synchronize the threads.
    See attached code for an implementation of all 3 flavors. There is no error-checking for brevity, but be sure to add error checking in your code

    gg
    Attached Files Attached Files
    Last edited by Codeplug; May 20th, 2008 at 12:54 PM.

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured