需要在指针变量周围加上互斥锁吗?

4

在涉及指针间接引用(其中指针指向临界区的一部分数据)的代码段周围是否需要互斥锁?以下是一个示例代码:

struct list {
    int i;
    struct list *next;
};

int modify_second_elem(struct list *head, int val);
void * func1(void *ptr);
void * func2(void *ptr);

int modify_second_elem(struct list *head, int val) {

    if(head == NULL)
        return 1;

    /* Check to see if second element exists.
       Here, I am using indirection to get the next pointer.
       Would I need synchronization here? */
    if(head->next == NULL)
        return -1;

    pthread_mutex_lock(&list_lock);
    (head->next)->i = val;
    pthread_mutex_unlock(&list_lock);

    return 0;

}

void * func1(void *ptr) {
    struct list *head;
    head = (struct list *) ptr;
    modify_second_elem(head, 4);
}

void * func2(void *ptr) {
    struct list *head;
    head = (struct list *) ptr;
    modify_second_elem(head, 6);
}

void main() {
    struct list *el1, *el2, *el3;
    pthread_t th1, th2;

    el1 = (struct list *) malloc(sizeof(list));
    el2 = (struct list *) malloc(sizeof(list));
    el3 = (struct list *) malloc(sizeof(list));

    el1->i = 1;
    el1->next = el2;
    el2->i = 2;
    el2->next = el3;
    el3->i = 3;
    el3->next = NULL;

    pthread_create(&th1, NULL, &func1, (void *) el1);
    pthread_create(&th2, NULL, &func2, (void *) el1);

    pthread_join(th1, NULL);
    pthread_join(th2, NULL);

    exit(EXIT_SUCCESS);
}

1
当你有一个关键部分时,需要锁定。不要考虑指针或无指针。 - R. Martinho Fernandes
这是有效的代码吗?函数声明和定义之间的参数列表不同。 - knittl
“int modify_struct(struct s *sptr)” 和 “int modify_struct(s)” 是不同的。个人认为后者不是一个有效的函数。但也许我在这里漏掉了一些明显的东西。 - knittl
@knittl 你说得对。在写问题的过程中,我改变了我的代码,并将一个函数调用更改为函数定义,忘记用参数列表替换变量了。我甚至试图解释我的错误。我真是太傻了。我现在已经编辑好了。谢谢。 :) - torrential coding
没问题 :) 只是想确保这不是我在 C 语言中错过的东西 - knittl
4个回答

4

锁用于保护代码中的临界区。如果您的变量位于临界区中,则需要使用某种类型的锁来保护它。


谢谢。我不确定指向共享数据的指针是否也是关键的。 - torrential coding

4
没有足够的信息来给出一个真正好的答案。 s 如何“发布”到其他线程?其他线程如何“订阅”sstruct s 对象存储在什么数据结构中?
因此,我将给出一个通用的答案:
每一种在线程之间共享的数据都需要同步。这包括共享指针。
关于指针:
您可能听说过,在一些常见的 CPU 上,正确对齐指针的加载/存储是原子的(这并不适用于所有 CPU 或所有类型的指针,例如:x86 上的远指针是非原子的)。如果您不彻底了解 CPU / VM 内存模型,请避免使用此功能,否则如果您不使用锁定,则可能会出现许多微妙的问题(锁提供相当强的保证)。
编辑:
在您的示例中,既没有 th1 也没有 th2 修改列表,它们只修改列表的元素。因此,在这种特定情况下,您不需要锁定列表,只需要锁定列表的元素即可(指针在概念上属于链接列表实现)。
在更典型的情况下,一些线程将遍历列表,而另一些线程将修改列表(添加和删除元素)。这需要锁定列表,或使用某种无锁算法。
有几种方法可以进行此锁定。
- 在列表上拥有全局锁,并将其用于列表的元素。 - 使用分层锁定,在列表上有一个锁,每个元素上都有一个锁。要读取/修改元素,您首先需要锁定列表,找到元素,获取元素的锁,释放列表的锁,处理元素,最后释放元素的锁。如果您需要对元素进行一些复杂的处理并且不想阻止其他线程访问列表,则此选项很有用。您必须确保始终以相同的顺序获取锁,以避免死锁。

我已经修改了我的问题中的代码。您认为这足以得到更具体的评论吗?谢谢。 - torrential coding
我一直在线性地考虑锁的问题。了解到它们也可以按层次应用是很有趣的。所以,根据你所解释的,如果我有另一个线程来操作列表(添加/删除元素),那么在这种情况下,我也需要锁定“next”指针吗? - torrential coding
1
你需要对列表进行加锁,以保护整个列表数据结构(即所有的下一个指针和所有元素)。分层锁定可以看作是全局锁定的优化,它允许更多的并发。 - ninjalj

2

这取决于您对 s 进行的其他操作。例如,另一个线程修改了 s->c 的值,这取决于 s->i 是什么:

if (s->i == 1) {
    s->c = 'x';
} else {
    s->c = 'y';
}

在这种情况下,您不能省略互斥锁,否则可能会导致s->is->c同时被设置为1和'y'。

2
一把锁被用来保护关键区段。它与变量是否为指针无关。
在您的情况下,您不需要锁定该检查。因为s是一个参数,所以它不能从函数外部访问。然而,由其指向的数据并不局限于您的函数,因此您可能需要锁定对该数据的访问。

谢谢你的回答。我是个新手,从来没有想过我实际上在处理一个局部变量。回过头来看,我的问题现在看起来确实很愚蠢。难怪谷歌没有返回任何结果。 - torrential coding
我在那里看不到任何局部变量。 - ninjalj
@ninjalj:好的,这是一个函数参数 :P。不过我的观点仍然适用。 - R. Martinho Fernandes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接