Why this example using mutex is less efficient compared to another one with additional condition variable?

半腔热情 提交于 2021-01-28 11:01:42

问题


An example from The Linux Programming Interface:

In the producer threads, we would have code such as the following:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int avail = 0;

/* Code to produce a unit omitted */
s = pthread_mutex_lock(&mtx);
if (s != 0)
    errExitEN(s, "pthread_mutex_lock");

avail++; /* Let consumer know another unit is available */

s = pthread_mutex_unlock(&mtx);
if (s != 0)
    errExitEN(s, "pthread_mutex_unlock");

And in the main (consumer) thread, we could employ the following code:

for (;;) {
    s = pthread_mutex_lock(&mtx);
    if (s != 0)
        errExitEN(s, "pthread_mutex_lock");
    while (avail > 0) {
        /* Consume all available units */
        /* Do something with produced unit */
        avail--;
    }
    s = pthread_mutex_unlock(&mtx);
    if (s != 0)
        errExitEN(s, "pthread_mutex_unlock");
}

The above code works, but it wastes CPU time, because the main thread continually loops, checking the state of the variable avail. A condition variable remedies this problem. It allows a thread to sleep (wait) until another thread notifies (signals) it that it must do something.


The book then gives what he thinks is a better version, with condition variable:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0;

Producer thread:

/* Code to produce a unit omitted */
s = pthread_mutex_lock(&mtx);
if (s != 0)
    errExitEN(s, "pthread_mutex_lock");

avail++;  /* Let consumer know another unit is available */

s = pthread_mutex_unlock(&mtx);
if (s != 0)
    errExitEN(s, "pthread_mutex_unlock");

s = pthread_cond_signal(&cond); /* Wake sleeping consumer */
if (s != 0)
    errExitEN(s, "pthread_cond_signal");

Consumer thread:

for (;;) {
    s = pthread_mutex_lock(&mtx);
    if (s != 0)
        errExitEN(s, "pthread_mutex_lock");
    while (avail == 0) {            /* Wait for something to consume */
        s = pthread_cond_wait(&cond, &mtx);
        if (s != 0)
            errExitEN(s, "pthread_cond_wait");
    }

    while (avail > 0) {             /* Consume all available units */
        /* Do something with produced unit */
        avail--;
    }

    s = pthread_mutex_unlock(&mtx);
    if (s != 0)
        errExitEN(s, "pthread_mutex_unlock");
    /* Perhaps do other work here that doesn't require mutex lock */
}

Question: Why the first version is less effcient(wastes CPU time) compared to the second one? I can't see the difference.

Btw, could you give me an example which may illustrate the author's point of view, which I think is:
You can use mutex along with condition variable to improve perfomance, compared to the one that uses only mutex.


回答1:


To start off, both are problematic. You want the producer to produce items outside the lock, and use the lock only to enqueue the job and notify the consumer about it, and you weant the consumer to wait and dequeue inside the lock, but to "do something" with the job OUTSIDE of the lock.

Now, regarding the condition variable: A mutex allows you to synchronize the code of two or more threads to ensure that the code within the mutex runs with no interruptions in order to maintain data integrity: if 2 threads run i++ at the same time you have no guarantee that i will grow exactly by 2. So the mutex here solves the problem of enqueuing/dequeuing from one queue (or in this case the avail variable) at the same time, but you do not have a mechanism to actually let the consumer sleep so not to waste precious CPU that can be used by the producer in order to produce more work. This is where the condition variable comes to help the consumer to sleep until the producers sets avail>0 and wakes the consumer up.

Example producer:

while (true) {
    // produce outside the lock
    ...

    pthread_mutex_lock(&mtx);   // lock
    ++avail;                    // update new job 
    pthread_cond_signal(&cond); // wake up consumer
    pthread_mutex_unlock(&mtx); // unlock
}

Example consumer:

while (true) {
    pthread_mutex_lock(&mtx);   // lock
    while (avail<=0)            // wait for new job
        pthread_cond_wait(&cond, &mtx);
    --avail;                    // get the new job 
    pthread_mutex_unlock(&mtx); // unlock

    // consume job outside the lock
}

Note that I have removed error handling for simplification

For more reading



来源:https://stackoverflow.com/questions/59479143/why-this-example-using-mutex-is-less-efficient-compared-to-another-one-with-addi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!