在 C 中将 int ** 传递给 pthread_create

Passing int ** to pthread_create in C

我正在尝试用 C 构建一个基本的“游戏”服务器,我将接受的套接字的每个文件描述符存储在 int * 中,每一个都存储在 int ** clients 中。然后我创建一个线程并传递 clients 作为它的参数。

代码如下:

game_server_threaded.c

#define PLAYERS 4
int ** clients;
/* Setting up server... */
    clients = malloc(sizeof(int *) * PLAYERS);

    while (1)
    {   
        for (int i = 0; i < PLAYERS; i++) {
            clients[i] = malloc(sizeof(int));
            *clients[i] = accept(server_sock, 
                (struct sockaddr *)&client_address, &size);
            printf("s%d: %d\n", i, *clients[i]);
        }

        pthread_t th;
        if (pthread_create(&th, NULL, play_game, clients) < 0)
            die("create thread");
    }

void * play_game(void * s)

void * play_game(void * s)
{
    int ** clients = (int **) s;

    srand (time(NULL));
    int k = rand() % MAX_K;
    int tries = 3;
    int turn = 0;

    for (int i = 0; i < PLAYERS; i++)
    {
        printf("s%d: %d\n", i, *clients[i]);
    }

    /* Game mechanics... */
}

但是,输出很奇怪。在线程中,设置了所有 *clients[i],除了 *clients[0].

这里是:

s0: 4
s1: 5
s2: 6
s3: 7

s0: 0
s1: 5
s2: 6
s3: 7

我已经用不同的演员等等广泛地测试了这个。我还创建了一个将此结构发送到线程的版本:

struct player_threads {
    char * msg;
    char c;
    int ** clients;
};

线程中,msgc完好无损,但clients的第一个元素还是0!

我设法用一个简单的 int * clients 让它工作,但我仍然无法理解这种行为,有人可以向我解释一下吗?

谢谢。

编辑****

上述代码出处的文件比较短,如果要复现可以在这里找到。

https://pastebin.com/61nRKvQf

创建的线程正在共享它们希望保持稳定但实际上正在线程外修改的数据。

具体来说,clients 指向 int * 的“数组”的内存是在创建任何线程之前分配的。它在创建每个新线程之前用一组新的 int * 填充,然后在 pthread_create 调用的最后一个参数中传递给新线程。当新线程检查 int * 的“数组”时,它可能会看到 pthread_create 调用时的原始元素加上一些已被主线程覆盖的元素。此外,由于不能保证对指针类型对象的赋值是原子的,因此线程也有可能访问部分更新(因此无效)的指针值。

在给出的示例中,前四行由主线程通过以下代码打印:

        for (int i = 0; i < PLAYERS; i++) {
            clients[i] = malloc(sizeof(int));
            *clients[i] = accept(server_sock, 
                (struct sockaddr *)&client_address, &size);
            printf("s%d: %d\n", i, *clients[i]);
        }

输出:

s0: 4
s1: 5
s2: 6
s3: 7

然后主线程创建一个新线程:

        pthread_t th;
        if (pthread_create(&th, NULL, play_game, clients) < 0)
            die("create thread");

然后主线程重复上面的for循环。首先要做的是用来自 malloc(sizeof(int)) 的新指针值覆盖 clients[0],然后它会阻塞 accept 调用。

与此同时,线程启动并打印 clients 数组中的内容,但 clients[0] 可能已被覆盖。

输出:

(此时主线程已经覆盖了client[0]。此时client[0][0]的内容不确定,但恰好这里包含0。) ...

s0: 0

(此时,主线程在调用 accept 时被阻塞,因此 clients[1]clients[3] 仍然具有与调用时相同的值呼叫 pcreate_thread.) ...

s1: 5
s2: 6
s3: 7

要解决此问题,需要为每个线程分配一个新的 clients 块。这可以通过将 clients 的分配移动到创建新线程的循环中来完成:

#define PLAYERS 4
int ** clients;
/* Setting up server... */

    while (1)
    {   
        clients = malloc(sizeof(int *) * PLAYERS);
        for (int i = 0; i < PLAYERS; i++) {
            clients[i] = malloc(sizeof(int));
            *clients[i] = accept(server_sock, 
                (struct sockaddr *)&client_address, &size);
            printf("s%d: %d\n", i, *clients[i]);
        }

        pthread_t th;
        if (pthread_create(&th, NULL, play_game, clients) < 0)
            die("create thread");
    }

顺便说一句,在使用 int **clients 时没有必要的间接寻址,因为在此示例中真正传递的是每个玩家一个 int 值(包含已接受的文件描述符)该播放器的套接字连接)。可以简化如下:

主线程

#define PLAYERS 4
int * clients;
/* Setting up server... */

    while (1)
    {
        clients = malloc(sizeof(int) * PLAYERS);
        for (int i = 0; i < PLAYERS; i++) {
            clients[i] = accept(server_sock, 
                (struct sockaddr *)&client_address, &size);
            printf("s%d: %d\n", i, clients[i]);
        }

        pthread_t th;
        if (pthread_create(&th, NULL, play_game, clients) < 0)
            die("create thread");
    }

游戏话题

void * play_game(void * s)
{
    int * clients = s;

    srand (time(NULL));
    int k = rand() % MAX_K;
    int tries = 3;
    int turn = 0;

    for (int i = 0; i < PLAYERS; i++)
    {
        printf("s%d: %d\n", i, clients[i]);
    }

    /* Game mechanics... */
}

对于将指向 struct player_threads 的指针传递给每个线程的版本,clients 成员类型简化为 int *:

struct player_threads

struct player_threads {
    char * msg;
    char c;
    int * clients;
};

主线程

#define PLAYERS 4
struct player_threads * players;
/* Setting up server... */

    while (1)
    {
        players = malloc(sizeof(*players));
        players->clients = malloc(sizeof(int) * PLAYERS);
        players->msg = "Hello";
        players->c = 'X';
        for (int i = 0; i < PLAYERS; i++) {
            players->clients[i] = accept(server_sock, 
                (struct sockaddr *)&client_address, &size);
            printf("s%d: %d\n", i, players->clients[i]);
        }

        pthread_t th;
        if (pthread_create(&th, NULL, play_game, players) < 0)
            die("create thread");
    }

游戏话题

void * play_game(void * s)
{
    struct player_threads * players = s;

    srand (time(NULL));
    int k = rand() % MAX_K;
    int tries = 3;
    int turn = 0;

    for (int i = 0; i < PLAYERS; i++)
    {
        printf("s%d: %d\n", i, players->clients[i]);
    }

    /* Game mechanics... */
}

另一方面,最好将对 srandrand 的调用替换为对 rand_r 的调用,因为 rand 调用可能不是 thread-safe:

    unsigned int seed = time(NULL);
    int k = rand_r(&seed) % MAX_K;

seed 需要存储在线程本地。在上面,它在线程的堆栈上,但作为 struct player_threads:

的成员为其提供存储可能会更好
struct player_threads {
    char * msg;
    char c;
    unsigned int seed;
    int * clients;
};
    players->seed = time(NULL);
    int k = rand_r(&players->seed) % MAX_K;