萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Linux 多線程同步機制:深入解析消息隊列

消息隊列是 Linux 核心中維護的消息鏈表,透過唯一的隊列標識符進行管理,實現了進程或線程之間的異步通信機制。它允許發送者將消息放入隊列,接收者則在稍後從隊列中讀取,雙方無需同步等待,極大地提升了並發程式的靈活性與解耦能力。

核心 API 速覽

  • msgget:建立新的消息隊列或取得現有隊列的識別碼。
  • msgsnd:將消息發送至隊列。每條消息包含一個 long 類型的 mtype(消息類型)與資料緩衝區。
  • msgrcv:從隊列讀取消息。讀取策略極具彈性,不強制遵循先進先出(FIFO)原則,可依消息類型篩選。

1. msgget — 建立或開啟消息隊列

#include <sys/msg.h>
int msgget(key_t key, int flag);
// 成功返回消息隊列 ID;失敗返回 -1

key 通常由 ftok() 產生,flag 常用 IPC_CREAT | 0666 表示建立並設定權限。

2. msgsnd — 發送消息

#include <sys/msg.h>
int msgsnd(int msgid, const void *ptr, size_t nbytes, int flag);
// 成功返回 0;失敗返回 -1

flag 選項決定阻塞行為:

  • 指定 IPC_NOWAIT:隊列已滿時立即返回 EAGAIN 錯誤,不阻塞。
  • 不指定:預設阻塞,直到隊列有可用空間為止。

3. msgrcv — 讀取消息

ssize_t msgrcv(int msgid, void *ptr, size_t nbytes, long type, int flag);

type 參數控制讀取策略:

  • type == 0:讀取隊列第一條消息(FIFO)。
  • type > 0:讀取類型值等於 type 的第一條消息。
  • type < 0:讀取類型值 ≤ |type| 的消息;若有多條符合,取類型值最小者。

4. 消息隊列建立與使用流程

#define MSG_FILE "."
#define BUFFER 255

struct msgtype {
    long mtype;
    char buffer[BUFFER + 1];
};

key_t key;
int msgid;
struct msgtype msg;

/* 1. 產生鍵值 */
if ((key = ftok(MSG_FILE, 'a')) == -1) {
    perror("ftok");
    exit(1);
}

/* 2. 建立/開啟隊列 */
if ((msgid = msgget(key, IPC_CREAT | 0666)) == -1) {
    perror("msgget");
    exit(1);
}

/* 3. 發送消息 */
msg.mtype = 1;
strncpy(msg.buffer, "Hello Queue", BUFFER);
msgsnd(msgid, &msg, sizeof(msg.buffer), 0);

/* 4. 接收消息 */
msgrcv(msgid, &msg, sizeof(msg.buffer), 1, 0);
printf("Received: %s\n", msg.buffer);

5. 完整範例:主線程與工作線程雙向通信

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <pthread.h>

#define MSG_FILE "."
#define BUFFER 255
#define PERM (S_IRUSR | S_IWUSR)

struct msgtype {
    long mtype;
    char buffer[BUFFER + 1];
};

void *thr_test(void *arg) {
    struct msgtype msg;
    int msgid = *((int *)arg);
    printf("Thread: msgid=%d\n", msgid);

    /* 接收類型 1 的消息 */
    msgrcv(msgid, &msg, sizeof(msg.buffer), 1, 0);
    fprintf(stderr, "Server Received: %s\n", msg.buffer);

    /* 回覆類型 2 的消息 */
    msg.mtype = 2;
    strcpy(msg.buffer, "ACK from thread");
    msgsnd(msgid, &msg, sizeof(msg.buffer), 0);
    pthread_exit((void *)2);
}

int main(int argc, char **argv) {
    struct msgtype msg;
    key_t key;
    int msgid;
    pthread_t tid;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(1);
    }

    if ((key = ftok(MSG_FILE, 'a')) == -1) {
        perror("ftok");
        exit(1);
    }
    if ((msgid = msgget(key, IPC_CREAT | 0666)) == -1) {
        perror("msgget");
        exit(1);
    }

    fprintf(stderr, "Message Queue ID: %d\n", msgid);
    pthread_create(&tid, NULL, thr_test, &msgid);

    /* 主線程發送類型 1 */
    msg.mtype = 1;
    strncpy(msg.buffer, argv[1], BUFFER);
    msgsnd(msgid, &msg, sizeof(msg.buffer), 0);

    /* 等待線程結束 */
    pthread_join(tid, NULL);
    return 0;
}

透過上述範例可見,消息隊列配合 mtype 欄位,能輕易實現多線程間的請求-回應模式,是 Linux 進程間通信(IPC)的經典利器。

copyright © 萬盛學電腦網 all rights reserved