What are the common causes for high CPU usage?

前端 未结 8 688
半阙折子戏
半阙折子戏 2020-12-13 08:19

Background:

In my application written in C++, I have created 3 threads:

  • AnalysisThread (or Producer) : it reads an input file, parses
8条回答
  •  旧时难觅i
    2020-12-13 08:34

    Although the others have correctly analysed the problem already (as far as I can tell), let me try to add some more detail to the proposed solutions.

    Firstly, to summarize the problems: 1. If you keep your consumer thread busy spinning in a for-loop or similar, that's a terrible waste of CPU power. 2. If you use the sleep() function with a fixed number of milliseconds, it is either a waste of CPU, too (if the time amount is too low), or you delay the process unnecessarily (if it's too high). There is no way to set the time amount just right.

    What you need to do instead is to use a type of sleep that wakes up just at the right moment, i.e. whenever a new task has been appended to the queue.

    I'll explain how to do this using POSIX. I realize that's not ideal when you are on Windows, but, to benefit from it, you can either use POSIX libraries for Windows or use corresponding functions available in your environment.

    Step 1: You need one mutex and one signal:

    #include 
    pthread_mutex_t *mutex  = new pthread_mutex_t;
    pthread_cond_t  *signal = new pthread_cond_t;
    
    /* Initialize the mutex and the signal as below.
       Both functions return an error code. If that
       is not zero, you need to react to it. I will
       skip the details of this. */
    pthread_mutex_init(mutex,0);
    pthread_cond_init(signal,0);
    

    Step 2: Now inside the consumer thread, wait for the signal to be sent. The idea is that the producer sends the signal whenever it has appended a new task to the queue:

    /* Lock the mutex. Again, this might return an error code. */
    pthread_mutex_lock(mutex);
    
    /* Wait for the signal. This unlocks the mutex and then 'immediately'
       falls asleep. So this is what replaces the busy spinning, or the
       fixed-time sleep. */
    pthread_cond_wait(signal,mutex);
    
    /* The program will reach this point only when a signal has been sent.
       In that case the above waiting function will have locked the mutex
       right away. We need to unlock it, so another thread (consumer or
       producer alike) can access the signal if needed.  */
    pthread_mutex_unlock(mutex);
    
    /* Next, pick a task from the queue and deal with it. */
    

    Step 2 above should essentially be placed inside an infinite loop. Make sure there is a way for the process to break out of the loop. For example -- although slightly crude -- you can append a 'special' task to the queue that means 'break out of the loop'.

    Step 3: Enable the producer thread to send a signal whenever it has appended a task to the queue:

    /* We assume we are now in the producer thread and have just appended
       a task to the queue. */
    /* First we lock the mutex. This must be THE SAME mutex object as used
       in the consumer thread. */
    pthread_mutex_lock(mutex);
    
    /* Then send the signal. The argument must also refer to THE SAME
       signal object as is used by the consumer. */
    pthread_cond_signal(signal);
    
    /* Unlock the mutex so other threads (producers or consumers alike) can
       make use of the signal. */
    pthread_mutex_unlock(mutex);
    

    Step 4: When everything is finished and you shut down your threads, you must destroy the mutex and the signal:

    pthread_mutex_destroy(mutex);
    pthread_cond_destroy(signal);
    delete mutex;
    delete signal;
    

    Finally let me re-iterate one thing the others have said already: You must not use an ordinary std::deque for concurrent access. One way of solving this is to declare yet another mutex, lock it before every access to the deque, and unlock it right after.

    Edit: A few more words about the producer thread, in light of the comments. As far as I understand it, the producer thread is currently free to add as many tasks to the queue as it can. So I suppose it will keep doing that and keep the CPU busy to the extent that it isn't delayed by IO and memory access. Firstly, I don't think of the high CPU usage resulting from this as a problem, but rather as a benefit. However, one serious concern is that the queue will grow indefinitely, potentially causing the process to run out of memory space. Hence a useful precaution to take would be to limit the size of the queue to a reasonable maximum, and have the producer thread pause whenever the queue grows too long.

    To implement this, the producer thread would check the length of the queue before adding a new item. If it is full, it would put itself to sleep, waiting for a signal to be sent by a consumer when taking a task off the queue. For this you could use a secondary signal mechanism, analogous to the one described above.

提交回复
热议问题