更好地理解并发(带锁)

问题描述 投票:0回答:3

我试图理解并发性,并更好地使用锁,但是我制作的这个虚拟示例使我失望:

int i = 0;

void foo() {
    int n = i;
    i = i + 1;
    printf("foo: %d\n", n);
}

void boo() {
    int n = i;
    i = i + 1;
    printf("boo: %d\n", n);
}

int main(int argc, char* argv[]) {

    pthread_t p1, p2;

    pthread_create(&p1, NULL, (void*) foo, NULL);
    pthread_create(&p2, NULL, (void*) boo, NULL);

    // wait for threads to finish
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // final print
    printf("main: %d\n", i);

    return 0;
}

[如果我理解正确,i = i + 1;foo()中的bar()可能会导致某些意外行为。一种意外的行为是我们将同时获得“ foo:0”和“ bar:0”,因为有可能在i = i + 1;之前进行上下文切换,因此n始终为0。我认为预期的行为是“ foo:0”“ bar:1”或“ bar:0”“ foo:1”(如果我错了,请纠正我)。

为了解决这个问题,我添加了锁:

int i = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void foo() {
    int n = i;
    i = i + 1;
    printf("foo: %d\n", n);
}

void boo() {
    int n = i;
    i = i + 1;
    printf("boo: %d\n", n);
}

int main(int argc, char* argv[]) {

    pthread_t p1, p2;

    printf("Locking foo\n");
    pthread_mutex_lock(&lock);
    printf("Locked foo\n");
    pthread_create(&p1, NULL, (void*) foo, NULL);
    pthread_mutex_unlock(&lock);
    printf("Unlocked foo\n");

    printf("Locking boo\n");
    pthread_mutex_lock(&lock);
    printf("Locked boo\n");
    pthread_create(&p2, NULL, (void*) boo, NULL);
    pthread_mutex_unlock(&lock);
    printf("Unlocked boo\n");

    // wait for threads to finish
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // final print
    printf("main: %d\n", i);

    return 0;
}

我认为这可以解决意外的结果,但是运行此命令时我得到了令人惊讶的输出:

Locking foo
Locked foo
Unlocked foo
Locking boo
Locked boo
foo: 0
Unlocked boo
boo: 1
main: 2

似乎程序锁定了第一个调用foo()的线程,然后立即将其解锁,而没有实际执行printf?然后,它继续锁定调用boo()的线程,并按顺序处理奇怪的事情。有人可以解释这种行为吗?我以为输出看起来像:

Locking foo
Locked foo
foo: 0
Unlocked foo
Locking boo
Locked boo
boo: 1
Unlocked boo
main: 2
c multithreading concurrency operating-system
3个回答
1
投票

您使用的锁不正确。您锁定互斥锁,启动线程,然后将其解锁。线程在不了解锁定操作的情况下运行。使用共享内存功能中的锁:

void foo() {
    pthread_mutex_lock(&lock);
    int n = i;
    i = i + 1;
    pthread_mutex_unlock(&lock);
    printf("foo: %d\n", n);
}

boo功能相同。


0
投票

您选择的措词背叛了可能造成的严重误会:

似乎程序锁定了调用foo()的第一个线程

程序不锁定线程。相反,线程获取锁。那也可以包括程序的主线程。在协作(!)线程之间可以实现互斥,这是因为一次只有一个线程可以持有任何特定的锁(mutex)。]

因此,如果线程B在尝试获取线程A时锁定了给定的互斥锁,则线程A的获取尝试将被阻塞(pthread_mutex_lock()调用的返回将被延迟)。线程A在获取互斥量之前不会继续进行。因此,关键区域的边界由对同一互斥锁上的pthread_mutex_lock()pthread_mutex_unlock()的调用定义。大致来说,每个参与线程必须在访问共享共享变量之前获取适当的互斥体,并且每个互斥体在完成互斥体后必须释放互斥体,以允许其他线程依次获取它。

其他答案已经提供了有关示例程序外观的详细信息。


0
投票

锁定应该在此处的功能中进行:

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

int i = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void foo() {
    printf("Locking foo\n");
    pthread_mutex_lock(&lock);
    printf("Locked foo\n");
    int n = i;
    i = i + 1;
    pthread_mutex_unlock(&lock);
    printf("Unlocked foo\n");
    printf("foo: %d\n", n);
}

void boo() {
    printf("Locking boo\n");
    pthread_mutex_lock(&lock);
    printf("Locked boo\n");
    int n = i;
    i = i + 1;
    pthread_mutex_unlock(&lock);
    printf("Unlocked boo\n");
    printf("boo: %d\n", n);
}

int main(int argc, char* argv[]) {

    pthread_t p1, p2;
    pthread_create(&p1, NULL, (void*) foo, NULL);
    pthread_create(&p2, NULL, (void*) boo, NULL);

    // wait for threads to finish
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // final print
    printf("main: %d\n", i);

    return 0;
}

这样,当一个功能锁定时,另一功能将被锁定,直到解锁为止。

© www.soinside.com 2019 - 2024. All rights reserved.