在 C 中使用共享变量和互斥锁进行线程同步

Thread synchronizing using shared variables and mutex in C

我正在尝试使用共享变量和互斥锁来同步三个 pthread,这样它们就会创建输出: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;
}

当多个线程同时尝试获取互斥量时,其中任何一个都可以获得它。因此,如果 "wrong" 线程获取互斥锁,它必须以某种方式让出,以便正确的线程获取互斥锁。在 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

将多个变量传递给线程函数的方式与传递字符的方式非常相似。您只使用结构而不是 char。例如:

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() wakes up one thread already waiting on the condition variable, and pthread_cond_broadcast() 唤醒所有已经在等待条件变量的线程。

在正常情况下,我们只唤醒一个线程,尽量避免所谓的thundering herd problem。只有三个线程并不是真正的问题,但我认为在这里引入这个概念可能是个好主意。只有当我们发现当前线程不是正确的线程时,我们才会唤醒所有等待条件变量的线程。

如果我们只在条件变量上发出信号,那么两个错误的线程可能会交替出现;这就是我们真正需要广播的原因。