Лучшее понимание параллелизма (с блокировками) - PullRequest
2 голосов
/ 07 апреля 2020

Я пытаюсь понять параллелизм и лучше использовать блокировки, но этот глупый пример, который я сделал, отбрасывает меня:

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

Ответы [ 3 ]

2 голосов
/ 07 апреля 2020

Ваш выбор формулировки выдает вероятное серьезное недоразумение:

Похоже, что программа заблокировала первый поток, который вызывает foo ()

Программы не блокируют потоки , Скорее, потоки получают блокировки (или, что эквивалентно, блокируют мьютексы потоков). Это также может включать основной поток программы. Взаимное исключение достигается среди взаимодействующих (!) Потоков тем, что только один поток может одновременно удерживать любую конкретную блокировку (мьютекс).

Таким образом, если поток B заблокировал данный мьютекс, когда поток A пытается получить его, тогда попытка получения потока А заблокирует (возврат вызова pthread_mutex_lock() будет отложен). Поток A не будет продолжаться, пока не получит мьютекс. Таким образом, границы критических областей определяются вызовами pthread_mutex_lock() и pthread_mutex_unlock() на одном и том же мьютексе. Приблизительно говоря, каждый участвующий поток должен получить соответствующий мьютекс перед доступом к общим разделяемым переменным, и каждый из них должен освободить мьютекс, когда это будет сделано, чтобы позволить другим потокам получать его по очереди.

В других ответах уже представлены подробности как это может выглядеть в вашем примере программы.

1 голос
/ 07 апреля 2020

Блокировка должна выполняться в следующих функциях:

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

Таким образом, когда одна функция блокируется, другая функция будет заблокирована, пока блокировка не будет разблокирована.

1 голос
/ 07 апреля 2020

Вы используете замки неправильно. Вы блокируете мьютекс, запускаете поток и разблокируете его. Поток работает без каких-либо знаний о блокирующих операциях. Используйте блокировку в функциях, разделяющих память:

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

Сделайте то же самое с функцией boo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...