Does this implementation of mutex locks result in undefined behavior?

Dav*_*vid 7 c locking pthreads thread-safety

I need to control the frequency at which main processes data. In the example, it just increases the value of a variable. I cannot use sleep inside of main because I need the frequency to be constant (and I don't know exactly how long does it take to process all the data). I just know for a fact that whatever processing I need to do takes less than 2 seconds, so I just need to prevent main from increasing x more than once every two seconds.

The solution I've found involves using two mutexes: locking one in main and unlocking it in an extra thread, and locking the other in extra and unlocking it in main. This extra thread sleeps for 2 seconds per cycle.

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void  *extra(void *arg)
{
    pthread_mutex_t *lock = (pthread_mutex_t *) arg;
    while(1) {
        pthread_mutex_unlock(&lock[0]);
        pthread_mutex_lock(&lock[1]);
        sleep(2);
    }
}

int main()
{
    int x = 0;

    pthread_mutex_t lock[2];
    pthread_mutex_init(&lock[0], NULL);
    pthread_mutex_init(&lock[1], NULL);

    pthread_mutex_lock(&lock[1]);

    pthread_t extra_thread;
    pthread_create(&extra_thread, NULL, &extra, lock);

    while(1) {
        x += 1;
        printf("%d\n", x);

        pthread_mutex_lock(&lock[0]);
        pthread_mutex_unlock(&lock[1]);
    }
}
Run Code Online (Sandbox Code Playgroud)

The Problem

The reason why this works is that main cannot lock lock[0] twice; it has to wait until extra unlocks it. However, according to The Open Group

Attempting to relock the mutex causes deadlock. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, undefined behavior results.

The Question

Based on this, I see two issues here:

  1. If main tries to lock lock[0] twice it should deadlock.
  2. extra unlocking lock[0], which was locked by main, should be undefined behavior.

Is my analysis correct?

And*_*nle 5

回答您的问题,

  1. 如果main尝试锁定lock[0]两次,则应死锁。

是的,会的。除非您使用递归互斥锁,否则您的子线程将永远无法像main总是将其锁定那样来锁定互斥锁。

  1. extra解锁lock[0],这是由主锁,应该是未定义的行为。

按照POSIX文档pthread_mutex_unlock(),这是一个不确定的行为NORMAL 和非稳健的互斥。但是,DEFAULT互斥对象不必一定NORMAL是非稳健的,因此有一个警告:

如果互斥锁类型为PTHREAD_MUTEX_DEFAULT,则pthread_mutex_lock()[和pthread_mutex_unlock()] 的行为可能对应于上表中所述的其他三个标准互斥锁类型之一。如果它不符合这三个条件之一,则对于标记的情况,行为是不确定的。

(请注意我的补充pthread_mutex_unlock()。互斥锁行为表清楚地表明,非所有者的解锁行为在不同类型的互斥锁之间有所不同,甚至在“非所有者时解锁”列中使用与“重新锁定”列,“匕首”标记是指我引用的脚注。)

如果非所有者线程尝试对其进行解锁,则健壮的NORMAL,ERRORCHECK或RECURSIVE互斥锁将返回错误,并且互斥锁保持锁定状态。

一个更简单的解决方案是使用一对信号量(以下代码故意缺少错误检查以及空行,否则它们将提高可读性,从而消除/减少任何垂直滚动条):

#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
sem_t main_sem;
sem_t child_sem;
void *child( void *arg )
{
    for ( ;; )
    {
        sem_wait( &child_sem );
        sleep( 2 );
        sem_post( &main_sem );
    }
    return( NULL );
}
int main( int argc, char **argv )
{
    pthread_t child_tid;
    sem_init( &main_sem, 0, 0 );
    sem_init( &child_sem, 0, 0 );
    pthread_create( &child_tid, NULL, child, NULL );
    int x = 0;
    for ( ;; )
    {
        // tell the child thread to go
        sem_post( &child_sem );
        // wait for the child thread to finish one iteration
        sem_wait( &main_sem );
        x++;
        printf("%d\n", x);
    }
    pthread_join( child_tid, NULL );
}
Run Code Online (Sandbox Code Playgroud)