1. Linux线程间消息通信简介
在Linux操作系统中,进程是资源分配的基本单位,而线程是程序执行的基本单位。线程是进程中的一个执行流,与进程共享地址空间、文件描述符、信号处理等资源,因此线程间的通信非常重要。
Linux提供了多种线程间消息通信的机制,包括管道、FIFO、消息队列、信号量、共享内存、套接字等。本文将重点介绍如何使用这些机制实现全面协作的线程间消息通信。
2. 管道通信
2.1 创建管道
管道是Linux提供的一种最简单的线程间通信机制,其基本操作包括创建管道、读写管道。
创建一个无名管道可以使用pipe函数:
int pipe(int pipefd[2]);
该函数的参数pipefd是一个长度为2的数组,其中pipefd[0]表示读端,pipefd[1]表示写端。调用pipe函数成功后,pipefd[0]和pipefd[1]分别是管道的读写端。
下面是一个示例代码:
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
上述代码创建了一个管道,并将读端和写端存储在pipefd数组中。
2.2 管道读写
管道的写操作使用write函数:
ssize_t write(int fd, const void *buf, size_t count);
其中fd是写端的文件描述符,buf是要写入的数据,count是写入数据的字节数。
管道的读操作使用read函数:
ssize_t read(int fd, void *buf, size_t count);
其中fd是读端的文件描述符,buf是读取数据的缓冲区,count是要读取的字节数。
以下是一个管道通信的示例代码:
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
int ret;
char buf[20];
ret = fork();
if (ret == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (ret == 0) {
close(pipefd[0]);
write(pipefd[1], "Hello from child", 16);
close(pipefd[1]);
exit(EXIT_SUCCESS);
} else {
close(pipefd[1]);
read(pipefd[0], buf, sizeof(buf));
printf("Received message: %s\n", buf);
close(pipefd[0]);
wait(NULL);
}
在上述代码中,子进程向管道中写入数据,父进程从管道中读取数据,并打印出接收到的消息。
3. 共享内存通信
3.1 创建共享内存
共享内存是一种高效的线程间通信方式,多个线程可以直接访问同一块内存空间,不需要通过中间介质进行数据的拷贝。
在Linux中,创建共享内存可以使用shmget函数:
int shmget(key_t key, size_t size, int shmflg);
其中key是共享内存的标识符,size是共享内存的大小,shmflg是标志选项。
以下是一个创建共享内存的示例代码:
int shmid;
key_t key = ftok("/tmp/mem", 'A'); /* 生成key */
shmid = shmget(key, 1024, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
上述代码首先生成一个唯一的key,然后调用shmget函数创建共享内存,指定大小为1024字节,并指定标志选项为IPC_CREAT | 0666。
3.2 共享内存的映射和访问
创建共享内存后,需要将其映射到进程的地址空间中才能访问。在Linux中,可以使用shmat函数将共享内存映射到进程的地址空间中:
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中shmid是共享内存的标识符,shmaddr是映射地址(通常设置为NULL),shmflg是标志选项。
以下是一个共享内存的映射和访问的示例代码:
char *shm;
shm = (char *)shmat(shmid, NULL, 0);
if (shm == (void *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
printf("Shared memory content: %s\n", shm);
shmdt(shm); /* 解除映射 */
上述代码将创建的共享内存映射到进程的地址空间中,并通过指针shm访问共享内存中的内容。最后调用shmdt函数解除映射。
4. 信号量通信
4.1 创建信号量
信号量是一种常用的线程间同步和通信机制,在Linux中可以使用semget函数创建信号量:
int semget(key_t key, int nsems, int semflg);
其中key是信号量的标识符,nsems是需要创建的信号量数量,semflg是标志选项。
以下是一个创建信号量的示例代码:
int semid;
key_t key = ftok("/tmp/sem", 'A'); /* 生成key */
semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
上述代码首先生成一个唯一的key,然后调用semget函数创建一个含有一个信号量的信号量集,指定标志选项为IPC_CREAT | 0666。
4.2 信号量的操作
创建信号量后,需要对其进行操作,常见的操作有P操作和V操作,即等待和释放信号量。
在Linux中,可以使用semop函数对信号量进行操作:
int semop(int semid, struct sembuf *sops, size_t nsops);
其中semid是信号量集的标识符,sops是包含nsops个struct sembuf结构体的数组,每个结构体表示一次信号量操作。
以下是一个信号量的操作的示例代码:
struct sembuf semops[1];
semops[0].sem_num = 0;
semops[0].sem_op = -1;
semops[0].sem_flg = SEM_UNDO;
if (semop(semid, semops, 1) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
printf("Semaphore acquired\n");
semops[0].sem_op = 1;
if (semop(semid, semops, 1) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
printf("Semaphore released\n");
上述代码首先定义一个包含一个信号量操作的数组,其中信号量编号为0,表示第一个信号量;sem_op为-1,表示等待信号量;sem_flg为SEM_UNDO,表示如果进程退出时未释放信号量,系统会自动释放。
然后调用semop函数进行P操作,并打印"Semaphore acquired"。最后再进行V操作,并打印"Semaphore released"。
5. 总结
本文介绍了Linux中实现线程间消息通信的几种常用方式,包括管道、共享内存、信号量。这些机制能够实现多个线程之间的数据传递和同步,具有不同的特点和适用场景。在实际应用中,应根据具体需求选择最合适的线程间消息通信方式。