这种互斥锁的实现会导致未定义的行为吗?

Does this implementation of mutex locks result in undefined behavior?

我需要控制 main 处理数据的频率。在这个例子中,它只是增加了一个变量的值。我不能在 main 内部使用 sleep 因为我需要频率恒定(而且我不知道处理所有数据需要多长时间)。我只知道一个事实,我需要做的任何处理都不到 2 秒,所以我只需要防止 main 每两秒增加 x 一次以上。

我找到的解决方案涉及使用两个互斥锁:在 main 中锁定一个并在 extra 线程中解锁它,在 extra 中锁定另一个并在 extra 中解锁它main。此 extra 线程每个周期休眠 2 秒。

#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]);
    }
}

问题

这样做的原因是main 不能锁定lock[0] 两次;它必须等到 extra 解锁它。然而,根据 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.

问题

基于此,我在这里看到两个问题:

  1. 如果 main 尝试锁定 lock[0] 两次,它应该死锁。
  2. extra 解锁被 main 锁定的 lock[0] 应该是未定义的行为。

我的分析是否正确?

理智的thread-safe解决方案是一个条件变量:

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

    pthread_mutex_lock(&lock);
    pthread_cond_wait(&cond, &lock);
    pthread_mutex_unlock(&lock);
}

然后在卧铺线程中执行:

//sleeper thread
while(1) {
    pthread_cond_signal(&cond);
    sleep(2);
}

然而,您也可以使用高分辨率睡眠和时间从操作系统读取当前时间和剩余时间的睡眠,直到下一个纪元。

下一个选项是使用 timerfd 以固定的时间间隔唤醒您。如果您错过了醒来,它可以让您知道。

回答您的问题,

  1. If main tries to lock lock[0] twice it should deadlock.

是的,会的。除非你使用递归互斥锁,否则你的子线程将永远无法锁定互斥锁,因为 main 总是会锁定它。

  1. extra unlocking lock[0], which was locked by main, should be undefined behavior.

根据 POSIX documentation for pthread_mutex_unlock(),这是 NORMAL non-robust 互斥锁的未定义行为。但是,DEFAULT 互斥量不一定是 NORMAL 和 non-robust,因此需要注意:

If the mutex type is PTHREAD_MUTEX_DEFAULT, the behavior of pthread_mutex_lock() [and pthread_mutex_unlock()] may correspond to one of the three other standard mutex types as described in the table above. If it does not correspond to one of those three, the behavior is undefined for the cases marked.

(请注意我添加的 pthread_mutex_unlock()。互斥行为的 table 清楚地表明 non-owner 的解锁行为在不同类型的互斥之间有所不同,甚至使用相同的 [= "Unlock When Not Owner"栏中的45=]标记与"Relock"栏中使用的一样,"dagger"标记指的是我引用的脚注。)

一个强大的 NORMALERRORCHECKRECURSIVE 互斥量将 return 如果 non-owning 线程试图解锁它,并且互斥量保持锁定状态。

一个更简单的解决方案是使用一对信号量(以下代码故意缺少错误检查和空行,否则会增加可读性以便 eliminate/reduce 任何垂直滚动条):

#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 );
}