我正在嘗試用 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;
};
在執行緒中,msg并且c是完整的,但客戶端的第一個元素仍然是 0!
我設法用一個簡單的方法讓它作業int * clients,但我仍然無法理解這種行為,有人可以向我解釋一下嗎?
謝謝你。
編輯****
上面代碼的來源檔案比較短,如果想復現可以在這里找到。
https://pastebin.com/61nRKvQf
uj5u.com熱心網友回復:
創建的執行緒正在共享他們希望保持穩定但實際上正在執行緒外修改的資料。
具體來說,由 指向的記憶體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回圈。做的第一件事是clients[0]用一個新的指標值覆寫,malloc(sizeof(int))然后它在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... */
}
對于將指向 a 的指標傳遞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... */
}
另外,最好將呼叫替換為呼叫,srand因為呼叫可能不是執行緒安全的:randrand_rrand
unsigned int seed = time(NULL);
int k = rand_r(&seed) % MAX_K;
seed needs to be in storage local to the thread. In the above, it is on the thread's stack, but it may be better to provide storage for it as a member of 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;
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/430569.html
