【问题标题】:How to safely and correctly destroy a mutex in Linux using pthread_mutex_destroy?如何使用 pthread_mutex_destroy 在 Linux 中安全、正确地销毁互斥锁​​?
【发布时间】:2018-03-02 21:13:59
【问题描述】:

我阅读了APUE 3rd,11.6.1 Mutexes,本章中有一个关于锁定和解锁互斥锁的示例:

struct foo {
    int             f_count;
    pthread_mutex_t f_lock;
    int             f_id;
    /* ... more stuff here ... */
};

struct foo *
foo_alloc(int id) /* allocate the object */
{
    struct foo *fp;

    if ((fp = malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            return(NULL);
        }
        /* ... continue initialization ... */
    }
    return(fp);
}

void
foo_hold(struct foo *fp) /* add a reference to the object */
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

void
foo_rele(struct foo *fp) /* release a reference to the object */
{
    pthread_mutex_lock(&fp->f_lock);
    if (--fp->f_count == 0) { /* last reference */
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    } else {
        pthread_mutex_unlock(&fp->f_lock);
    }
}

foo_rele中,pthread_mutex_unlockpthread_mutex_destroy之间存在竞争条件:B线程可以在A线程的pthread_mutex_unlockpthread_mutex_destroy之间调用pthread_mutex_lock 这将导致未定义的行为(“尝试销毁锁定的互斥体会导致未定义的行为”)。

我说的对吗?如果我是对的,那么如何使其正常工作或如何使用pthread_mutex_destroy 在 Linux 中安全正确地销毁互斥锁​​?

【问题讨论】:

    标签: c linux pthreads mutex race-condition


    【解决方案1】:

    pthread_mutex_destroy() 的 POSIX 规范说:

    销毁已解锁的已初始化互斥锁应该是安全的。

    这意味着如果线程 B 在 foo_rele()if 语句的 else 子句中调用 pthread_mutex_unlock() ,那么线程 A 调用 pthread_mutex_destroy() 是安全的,因为它只能在线程 B 之后到达那里pthread_mutex_unlock() 调用已解锁互斥锁。

    所有这一切都假设引用计数是正确的,因此在线程 A 解锁互斥锁后,其他一些线程无法将计数从 0 增加到 1。换句话说,在 refcount 下降到 0 的那一刻,不可能有另一个线程可能调用foo_hold()

    APUE 在示例代码后面的解释中提到了这一点:

    在这个例子中,我们忽略了线程在调用foo_hold之前是如何找到对象的。即使引用计数为零,如果另一个线程在调用foo_hold 时在互斥体上阻塞,那么foo_rele 释放对象的内存也是错误的。我们可以通过确保在释放内存之前找不到对象来避免这个问题。我们将在下面的示例中看到如何做到这一点。

    【讨论】:

    • 能否参考11.6.2 Deadlock Avoidance查看示例代码,我认为线程只能通过调用foo_find来增加对象引用,而不是@987654333直接@,对吗?
    • 是的,只有foo_find() 调用foo_hold()。当foo_rele() 发现它即将将引用计数减为0 时,它会在持有互斥锁的同时从哈希表中删除该项目,因此没有其他线程可以再次获得指向该foo 对象的指针。一旦发生这种情况,就可以解锁互斥锁,可以销毁 foo 对象中的互斥锁,并释放 foo 对象本身,因为没有其他东西可以获取该 foo 对象了。
    • 11.6.2 避免死锁章节有两个版本的示例代码,在版本一中,foo_find中的代码foo_hold(fp);应替换为:pthread_mutex_lock(&fp->f_lock); fp->f_count++; pthread_mutex_unlock(&fp->f_lock);和功能foo_hold 应该被删除。在第二版中,函数foo_hold应该只是被删除,它没有用,另一方面也很危险,用户可能会误用界面,对吧?
    • 你的建议是合理的。但是foo_hold() 也可以有合法用途。安全地增加 refcount 的一种方法是制定一个策略,即只有已经拥有 foo 对象的 ref 的线程负责增加 refcount,然后提供指向另一个线程的指针。如果一个拥有对象引用的线程负责递增引用计数,那么在递增操作期间引用计数不可能变为零。换句话说,只有已经拥有 ref 的线程才能调用foo_hold() 来提供指向另一个线程的指针。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    • 2021-03-30
    • 2013-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多