Синхронизация потоков с использованием общих переменных и мьютекса в C - PullRequest
0 голосов
/ 19 ноября 2018

Я пытаюсь синхронизировать три pthreads, используя общие переменные и мьютекс, чтобы они создавали выходные данные: 123123123 ...
Однако все, что я могу придумать, это использовать цикл while, как показано в коде ниже.
Можно ли сделать код более элегантным, не заставляя потоки спать и используя цикл while?

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

static pthread_mutex_t cs_mutex;
char p;
int q;

void* print(void *pParam)
{
    char c = *(char*)pParam;
    int i;

    for (i = 0; i < 100; i++)
    {
        while(p!=c) sleep(0.2);
        pthread_mutex_lock(&cs_mutex);
        printf("%c", c);
        fflush(stdout);
        q=(q+1)%4;
        if(q==0)q=1;
        p=q+48;
        pthread_mutex_unlock(&cs_mutex);
    }

    return 0;
}

int main(void)
{
    pthread_t hPrint1;
    pthread_t hPrint2;
    pthread_t hPrint3;

    pthread_mutex_init(&cs_mutex, NULL);

    char c1 = '1';
    char c2 = '2';
    char c3 = '3';

    p=c1;
    q=1;

    pthread_create(&hPrint1, NULL, print, (void*)&c1);
    pthread_create(&hPrint2, NULL, print, (void*)&c2);
    pthread_create(&hPrint3, NULL, print, (void*)&c3);

    getchar();

    pthread_mutex_destroy(&cs_mutex);

    return 0;
}

1 Ответ

0 голосов
/ 19 ноября 2018

Когда несколько потоков пытаются получить мьютекс одновременно, любой из них может получить его. Таким образом, если «неправильный» поток получает мьютекс, он должен каким-то образом уступить, чтобы правильный поток получил мьютекс. В коде OP sleep(0.2) пытается сделать это. (Ожидание занято и не работает должным образом, поскольку unistd.h sleep() принимает целое число секунд в качестве параметра.)

Лучшим вариантом будет использование мьютекса, условной переменной и индекса последовательности в качестве общей переменной. В псевдокоде каждый поток будет делать:

Function Thread(mynumber, mychar):

    Lock mutex

    Loop:
        Wait on condition variable
        If index >= limit:
            Signal on condition variable
            Unlock mutex
            Return
        Else
        If (index % mynumber == 0):
            Output mychar
            Signal on condition variable
        Else:
            Broadcast on condition variable
        End If
    End Loop
End Function

Способ передачи более одной переменной в функцию потока очень похож на способ передачи символа. Вместо символа вы просто используете структуру. Например:

struct work {
    int  mynumber;  /* Thread number: 0, 1, 2 */
    int  mychar;    /* Character to output: '1', '2', '3' */
};

Вы можете объявить struct work w[3]; как глобальную переменную или в main() и инициализировать ее, например,

    struct work w[3];
    w[0].mynumber = 0;  w[0].mychar = '1';
    w[1].mynumber = 1;  w[1].mychar = '2';
    w[2].mynumber = 2;  w[2].mychar = '3';

и обозначьте их адрес как &(w[0]) (или, что эквивалентно, просто &w[0]).

В функции потока вы можете использовать, например,

void *worker(void *payload)
{
    struct work *const w = payload;

    /* w->mynumber  is the number (0, 1, 2) of this thread,
       w->mychar    is the char ('1', '2', '3') to output */

Обратите внимание, что pthread_cond_signal() пробуждает один поток, уже ожидающий переменную условия, и pthread_cond_broadcast() пробуждает все потоки, уже ожидающие переменную условия.

В обычном случае мы просыпаемся только одним потоком, чтобы попытаться избежать так называемой проблемы грома 1010 *. Это не настоящая проблема только с тремя потоками, но я подумал, что, возможно, это хорошая идея, чтобы представить эту концепцию здесь. Только если мы обнаружим, что текущий поток не является правильным, мы пробуждаем все потоки, ожидающие переменную условия.

Если бы мы только сигнализировали о условной переменной, то вполне возможно, что два неправильных потока будут просто чередоваться; вот почему нам действительно нужна эта передача.

...