multithreading - 这是 glibc/pthread 中的错误吗?

标签 multithreading deadlock glibc

我正在使用一个强大的互斥体和一个条件。这在大多数情况下都有效,但很少会遇到僵局。 我无法将其简化为一个小的、可重现的示例,并且我认为这很可能是我的代码中的问题,但是,我注意到一些看起来可疑的东西:

当代码死锁时,一个线程处于pthread_cond_broadcast中:

#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f4ab2892970 in pthread_cond_broadcast@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_broadcast.S:133

另一个线程位于 pthread_mutex_lock 中,位于与条件一起使用的互斥体上:

#0  __lll_robust_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevelrobustlock.S:85
#1  0x00007f4ab288e7d7 in __pthread_mutex_lock_full (mutex=0x7f4a9858b128) at ../nptl/pthread_mutex_lock.c:256

正如你所看到的,pthread_mutex_lock使用lowlevelrobuSTLock,而pthread_cond_broadcast使用lowlevellock。该条件是否有可能在内部以某种方式使用非鲁棒互斥体?

我使用互斥体来保护共享内存,共享它的进程之一可能会被杀死。 那么,也许我的死锁发生是因为进程被杀死时位于 pthread_cond_broadcast 内部,而现在,其他进程无法广播,因为被杀死的进程仍然拥有互斥体?毕竟,类似的情况就是我首先开始使用强大的互斥体的原因。

PS:处理了进程在关键部分被终止的情况,健壮的互斥锁效果很好。对于所有死锁,我看到了 pthread_cond_broadcast 是事件函数的情况。

PPS:对于互斥体,有 pthread_mutexattr_setrobust,但我找不到类似 pthread_condattr_setrobust 的东西。存在吗?

最佳答案

编辑:

此“错误”已报告 here 。在这个特定的用例中,这只是条件变量的未定义行为。没有健壮的条件变量,因此它们不能在具有共享内存的 IPC 中使用。线程取消可能会使条件变量处于不一致的状态。

之前的答案如下:

我也有同样的问题。以下是导致 pthread_cond_broadcast 死锁的示例代码:

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

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define TRUE 1
#define FALSE 0

typedef struct {
    pthread_cond_t cond;
    pthread_mutex_t mtx;
    int predicate;
} channel_hdr_t;

typedef struct {
    int fd;
    channel_hdr_t *hdr;
} channel_t;

void printUsage() {
    printf("usage: shm_comm_test2 channel_name1 channel_name2\n");
}

int robust_mutex_lock(pthread_mutex_t *mutex) {
  // lock hdr mutex in the safe way
  int lock_status = pthread_mutex_lock (mutex);
  int acquired = FALSE;
  int err = -18;
  switch (lock_status)
  {
  case 0:
    acquired = TRUE;
    break;
  case EINVAL:
    printf("**** EINVAL ****\n");
    err = -12;
    break;
  case EAGAIN:
    printf("**** EAGAIN ****\n");
    err = -13;
    break;
  case EDEADLK:
    printf("**** EDEADLK ****\n");
    err = -14;
    break;
  case EOWNERDEAD:
    // the reader that acquired the mutex is dead
    printf("**** EOWNERDEAD ****\n");

    // recover the mutex
    if (pthread_mutex_consistent(mutex) == EINVAL) {
    printf("**** EOWNERDEAD, EINVAL ****\n");
      err = -15;
      break;
    }
    acquired = TRUE;
    break;
  default:
    printf("**** OTHER ****\n");
    // other error
    err = -18;
    break;
  }

  return acquired ? 0 : err;
}

int init_channel(char *shm_name, channel_t *out) {
    int initialize = FALSE;

    int shm_fd = shm_open (shm_name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
    if (shm_fd < 0) {
        if (errno == EEXIST) {
            // open again, do not initialize
            shm_fd = shm_open (shm_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
            if (shm_fd < 0) {
                printf( "ERROR: could not create %s, errno: %d\n", shm_name, errno );
                return 1;
            }
        }
        else {
            printf( "ERROR: could not create %s, errno: %d\n", shm_name, errno );
            return 2;
        }
    }
    else {
        // the shm object was created, so initialize it
        initialize = TRUE;

        printf("created shm object %s\n", shm_name);
        if (ftruncate (shm_fd, sizeof(channel_hdr_t)) != 0)
        {
            printf( "ERROR: could not ftruncate %s, errno: %d\n", shm_name, errno );
            close (shm_fd);
            shm_unlink (shm_name);
            return 3;
        }
    }

    void *ptr_shm_hdr = mmap (NULL, sizeof(channel_hdr_t), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    if (ptr_shm_hdr == MAP_FAILED)
    {
        printf( "ERROR: could not mmap %s, errno: %d\n", shm_name, errno );
        close (shm_fd);
        shm_unlink (shm_name);
        return 4;
    }

    channel_hdr_t *shm_hdr = ptr_shm_hdr;

    if (initialize) {
        // set mutex shared between processes
        pthread_mutexattr_t mutex_attr;
        pthread_mutexattr_init(&mutex_attr);
        pthread_mutexattr_setpshared (&mutex_attr, PTHREAD_PROCESS_SHARED);
        pthread_mutexattr_setrobust (&mutex_attr, PTHREAD_MUTEX_ROBUST);
        pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT);

        pthread_mutex_init (&shm_hdr->mtx, &mutex_attr);

        // set condition shared between processes
        pthread_condattr_t cond_attr;
        pthread_condattr_init(&cond_attr);
        pthread_condattr_setpshared (&cond_attr, PTHREAD_PROCESS_SHARED);
        pthread_cond_init (&shm_hdr->cond, &cond_attr);
    }

    shm_hdr->predicate = 0;
    out->fd = shm_fd;
    out->hdr = shm_hdr;

    return 0;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        printUsage();
        return 0;
    }

    char *shm_1_name = argv[1];
    char *shm_2_name = argv[2];

    channel_t ch_1;
    if (init_channel(shm_1_name, &ch_1) != 0) {
        return 1;
    }

    channel_t ch_2;
    if (init_channel(shm_2_name, &ch_2) != 0) {
        munmap( ch_1.hdr, sizeof(channel_hdr_t) );
        close( ch_1.fd );
        return 2;
    }

    int counter = 0;
    int counter2 = 0;
    while (TRUE) {
        ++counter;
        if (counter == 100000) {
            printf("alive %d\n", counter2);
            ++counter2;
            counter = 0;
        }
        int ret = robust_mutex_lock(&ch_1.hdr->mtx);
        if (ret != 0) {
            return ret;
        }
        ch_1.hdr->predicate = 1;
        pthread_cond_broadcast (&ch_1.hdr->cond);     // deadlock here
        pthread_mutex_unlock (&ch_1.hdr->mtx);



        ret = robust_mutex_lock(&ch_2.hdr->mtx);
        if (ret != 0) {
            return ret;
        }

        while (ch_2.hdr->predicate == 0 && ret == 0)
        {
            ret = pthread_cond_wait (&ch_2.hdr->cond, &ch_2.hdr->mtx);  // deadlock here
        }
        ch_2.hdr->predicate = 0;
        pthread_mutex_unlock (&ch_2.hdr->mtx);
    }

    munmap( ch_1.hdr, sizeof(channel_hdr_t) );
    close( ch_1.fd );

    munmap( ch_2.hdr, sizeof(channel_hdr_t) );
    close( ch_2.fd );

    return 0;
}

重现死锁:

  1. 使用参数运行程序的第一个实例:channel1channel2
  2. 使用参数运行程序的第二个实例:channel2 channel1
  3. 使用 Ctrl+C 中断两个程序
  4. 再次运行这两个程序

Ubuntu 16.04 中不存在该问题。 然而,它发生在 18.04。

两个死锁程序的回溯:

第一:

#0  0x00007f9802d989f3 in futex_wait_cancelable (private=<optimized out>, expected=0, futex_word=0x7f98031cd02c)
    at ../sysdeps/unix/sysv/linux/futex-internal.h:88
#1  __pthread_cond_wait_common (abstime=0x0, mutex=0x7f98031cd030, cond=0x7f98031cd000) at pthread_cond_wait.c:502
#2  __pthread_cond_wait (cond=0x7f98031cd000, mutex=0x7f98031cd030) at pthread_cond_wait.c:655
#3  0x00005648bc2af081 in main (argc=<optimized out>, argv=<optimized out>)
    at /home/dseredyn/ws_velma/ws_fabric/src/shm_comm/src/test2.c:198

第二:

#0  0x00007f1a3434b724 in futex_wait (private=<optimized out>, expected=3, futex_word=0x7f1a34780010)
    at ../sysdeps/unix/sysv/linux/futex-internal.h:61
#1  futex_wait_simple (private=<optimized out>, expected=3, futex_word=0x7f1a34780010)
    at ../sysdeps/nptl/futex-internal.h:135
#2  __condvar_quiesce_and_switch_g1 (private=<optimized out>, g1index=<synthetic pointer>, wseq=<optimized out>, 
    cond=0x7f1a34780000) at pthread_cond_common.c:412
#3  __pthread_cond_broadcast (cond=0x7f1a34780000) at pthread_cond_broadcast.c:73
#4  0x0000557a978b2043 in main (argc=<optimized out>, argv=<optimized out>)
    at /home/dseredyn/ws_velma/ws_fabric/src/shm_comm/src/test2.c:185

关于multithreading - 这是 glibc/pthread 中的错误吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51730660/

相关文章:

Java 并发计数器未正确清理

java - 线程被锁定在 java.util.Stack

multithreading - 简单的死锁示例

c++ - 为什么可以在g++(gcc)中重新定义gnulib?

c - sched_setaffinity 和 glibc - 交叉编译

java - 使用多线程从mysql表读取时如何限制记录数

multithreading - Akka - 调度员

c++ - 为什么这些线程不按顺序运行?

objective-c - 保存托管对象上下文会在iOS 5的performBlock中创建死锁

c++14 - 与库链接导致 char 中缺少 collat​​e facet