引言
在现代操作系统中,进程是程序运行的基本单位。为了实现复杂的功能,多个进程常常需要进行通信。进程间通信(Inter-Process Communication, IPC)是指多个进程之间进行数据交换的一种机制。IPC的主要目的包括数据传输、资源共享、通知事件和进程控制等。本文将深入探讨进程间通信的各种方式,包括管道、消息队列、共享内存和信号量等,从基础概念到实际应用,帮助你全面掌握进程间通信的知识和技巧。
1. 进程间通信介绍
1.1 进程间通信的目的
进程间通信的目的是为了满足多个进程在执行过程中需要相互协作的需求。主要有以下几个方面:
- 数据传输:一个进程需要将它的数据发送给另一个进程。例如,一个进程生成数据,另一个进程处理数据。
- 资源共享:多个进程需要共享同样的资源,例如共享内存区域或共享文件。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。例如,进程终止时通知父进程。
- 进程控制:有些进程希望完全控制另一个进程的执行。例如,调试进程希望能够拦截另一个进程的所有系统调用和异常,并能够及时知道它的状态改变。
1.2 进程间通信的发展
随着操作系统的发展,进程间通信的方式也在不断演进。主要包括:
- 管道:这是Unix系统中最古老的进程间通信形式。管道提供了一种简单的方式在父子进程之间传递数据。
- System V IPC:这是Unix System V引入的一系列进程间通信机制,包括消息队列、共享内存和信号量等。
- POSIX IPC:POSIX标准定义了一套独立于系统的进程间通信机制,包括消息队列、共享内存、信号量、互斥量、条件变量和读写锁等。
1.3 进程间通信的分类
根据通信的方式和实现机制,进程间通信可以分为以下几类:
-
管道:
- 匿名管道:用于具有亲缘关系的进程间通信。
- 命名管道:可以在无亲缘关系的进程间通信。
-
System V IPC:
- System V 消息队列:用于发送和接收消息。
- System V 共享内存:用于多个进程共享内存区域。
- System V 信号量:用于进程同步和互斥。
-
POSIX IPC:
- 消息队列:类似于System V消息队列,但接口不同。
- 共享内存:类似于System V共享内存,但接口不同。
- 信号量:类似于System V信号量,但接口不同。
- 互斥量:用于进程间互斥。
- 条件变量:用于进程间条件等待。
- 读写锁:用于读写操作的互斥和同步。
2. 管道
2.1 什么是管道
管道是Unix系统中最古老的进程间通信的形式之一。它提供了一种简单的方式在两个进程之间传递数据。管道可以看作是一个数据流,从一个进程的输出流到另一个进程的输入流。
2.2 匿名管道
匿名管道是一种无名的管道,通常用于具有亲缘关系的进程之间(如父子进程)。以下是创建匿名管道的基本步骤:
- 创建管道。
- 创建子进程。
- 父子进程分别关闭不需要的管道端口。
- 通过管道进行数据读写。
创建匿名管道的实例代码
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int fds[2];
char buf[100];
int len;
if (pipe(fds) == -1) {
perror("pipe");
exit(1);
}
// Read from stdin
while (fgets(buf, 100, stdin)) {
len = strlen(buf);
// Write into pipe
if (write(fds[1], buf, len) != len) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// Read from pipe
if ((len = read(fds[0], buf, 100)) == -1) {
perror("read from pipe");
break;
}
// Write to stdout
if (write(1, buf, len) != len) {
perror("write to stdout");
break;
}
}
return 0;
}
在这个实例中,我们创建了一个匿名管道,用于从标准输入读取数据,通过管道传输后再写入标准输出。
用fork来共享管道原理
使用fork
创建子进程后,父进程和子进程都拥有相同的文件描述符表。通过关闭不需要的管道端口,父进程和子进程可以各自使用管道的一端进行通信。例如:
c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char *argv[]) {
int pipefd[2];
if (pipe(pipefd) == -1) ERR_EXIT("pipe error");
pid_t pid = fork();
if (pid == -1) ERR_EXIT("fork error");
if (pid == 0) {
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = {0};
read(pipefd[0], buf, 10);
printf("buf=%s\n", buf);
return 0;
}
在这个例子中,父进程和子进程通过管道进行通信。子进程关闭读端,父进程关闭写端,实现数据的单向传输。
管道读写规则
管道的读写规则如下:
-
读操作:
- 当没有数据可读时:
- 非阻塞模式(
O_NONBLOCK
):read
调用返回-1,errno
值为EAGAIN
。 - 阻塞模式:
read
调用阻塞,直到有数据到达。
- 非阻塞模式(
- 当没有数据可读时:
-
写操作:
- 当管道满时:
- 非阻塞模式(
O_NONBLOCK
):write
调用返回-1,errno
值为EAGAIN
。 - 阻塞模式:
write
调用阻塞,直到有进程读走数据。
- 非阻塞模式(
- 当管道满时:
-
管道关闭:
- 所有写端关闭:
read
返回0,表示EOF。 - 所有读端关闭:
write
操作会产生SIGPIPE
信号,导致写进程退出。
- 所有写端关闭:
管道是半双工的,数据只能单向流动。如果需要双向通信,需要创建两个管道。
2.3 命名管道
命名管道(FIFO)是一种特殊类型的文件,可以在无亲缘关系的进程之间进行通信。命名管道可以通过命令行或程序创建。
创建命名管道的示例
命令行创建命名管道:
sh
$ mkfifo mypipe
程序创建命名管道:
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
if (mkfifo("mypipe", 0644) < 0) {
ERR_EXIT("mkfifo");
}
return 0;
}
使用命名管道进行通信
以下示例展示了如何使用命名管道实现文件拷贝:
读取文件并写入命名管道:
c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
int infd = open("source.txt", O_RDONLY);
if (infd == -1) ERR_EXIT("open");
int outfd = open("mypipe", O_WR
ONLY);
if (outfd == -1) ERR_EXIT("open");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0) {
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
从命名管道读取数据并写入目标文件:
c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
int infd = open("mypipe", O_RDONLY);
if (infd == -1) ERR_EXIT("open");
int outfd = open("destination.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1) ERR_EXIT("open");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0) {
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
命名管道的使用非常灵活,可以在无亲缘关系的进程之间实现数据传输。命名管道与匿名管道的主要区别在于创建和打开方式不同,一旦创建和打开后,它们具有相同的语义。
3. System V IPC
System V IPC包括消息队列、共享内存和信号量,是Unix System V引入的一系列进程间通信机制。System V IPC提供了更强大的功能和灵活性,适用于复杂的进程间通信需求。
3.1 System V 消息队列
消息队列是一种从一个进程向另一个进程发送数据块的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
消息队列的基本操作
消息队列的基本操作包括创建消息队列、发送消息和接收消息。
-
创建消息队列:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
-
发送消息:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
-
接收消息:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
消息队列的使用示例
以下示例展示了如何使用消息队列进行进程间通信:
创建和发送消息:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
```c
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key;
int msgid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((msgid = msgget(key, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("msgget");
}
struct msgbuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello, World!");
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
ERR_EXIT("msgsnd");
}
printf("Message sent: %s\n", msg.mtext);
return 0;
}
接收消息:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key;
int msgid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((msgid = msgget(key, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("msgget");
}
struct msgbuf msg;
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
ERR_EXIT("msgrcv");
}
printf("Message received: %s\n", msg.mtext);
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
ERR_EXIT("msgctl");
}
return 0;
}
在这个示例中,两个进程通过消息队列进行通信。一个进程发送消息,另一个进程接收消息。消息队列适用于需要发送不同类型消息的应用场景。
3.2 System V 共享内存
共享内存是最快的IPC形式之一。共享内存允许多个进程访问同一块内存区域,从而实现数据共享。共享内存不涉及内核数据传递,因此速度非常快。
共享内存的基本操作
共享内存的基本操作包括创建共享内存段、将共享内存段连接到进程地址空间、分离共享内存段和控制共享内存段。
-
创建共享内存段:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
-
连接共享内存段:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
-
分离共享内存段:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
-
控制共享内存段:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
共享内存的使用示例
以下示例展示了如何使用共享内存进行进程间通信:
创建共享内存段并写入数据:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define SHM_SIZE 1024
int main() {
key_t key;
int shmid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("shmget");
}
char *data = (char *)shmat(shmid, NULL, 0);
if (data == (char *)(-1)) {
ERR_EXIT("shmat");
}
strcpy(data, "Hello, Shared Memory!");
if (shmdt(data) == -1) {
ERR_EXIT("shmdt");
}
return 0;
}
连接共享内存段并读取数据:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define SHM_SIZE 1024
int main() {
key_t key;
int shmid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("shmget");
}
char *data = (char *)shmat(shmid, NULL, 0);
if (data == (char *)(-1)) {
ERR_EXIT("shmat");
}
printf("Data read from shared memory: %s\n", data);
if (shmdt(data) == -1) {
ERR_EXIT("shmdt");
}
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
ERR_EXIT("shmctl");
}
return 0;
}
在这个示例中,两个进程通过共享内存进行数据共享。一个进程将数据写入共享内存,另一个进程从共享内存读取数据。共享内存适用于需要高效数据传输的应用场景。
3.3 System V 信号量
信号量主要用于进程同步和互斥。信号量是一种计数器,用于控制对共享资源的访问。信号量可以实现多个进程之间的同步和互斥操作。
信号量的基本操作
信号量的基本操作包括创建信号量、控制信号量和删除信号量。
-
创建信号量:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
-
控制信号量:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...);
-
操作信号量:
c#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops);
信号量的使用示例
以下示例展示了如何使用信号量进行进程同步:
创建和初始化信号量:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
key_t key;
int semid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("semget");
}
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
ERR_EXIT("semctl");
}
printf("Semaphore initialized.\n");
return 0;
}
操作信号量实现同步:
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
void p(int semid) {
struct sembuf sb = {0, -1, 0};
if (semop(semid, &sb, 1) == -1) {
ERR_EXIT("semop p
");
}
}
void v(int semid) {
struct sembuf sb = {0, 1, 0};
if (semop(semid, &sb, 1) == -1) {
ERR_EXIT("semop v");
}
}
int main() {
key_t key;
int semid;
if ((key = ftok("progfile", 65)) == -1) {
ERR_EXIT("ftok");
}
if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) {
ERR_EXIT("semget");
}
pid_t pid = fork();
if (pid == -1) {
ERR_EXIT("fork");
}
if (pid == 0) {
// Child process
p(semid);
printf("Child process: entering critical section.\n");
sleep(2);
printf("Child process: leaving critical section.\n");
v(semid);
exit(EXIT_SUCCESS);
} else {
// Parent process
p(semid);
printf("Parent process: entering critical section.\n");
sleep(2);
printf("Parent process: leaving critical section.\n");
v(semid);
wait(NULL);
}
return 0;
}
在这个示例中,父进程和子进程通过信号量实现同步,确保在同一时间只有一个进程进入临界区。信号量适用于需要进程同步和互斥的应用场景。
4. POSIX IPC
POSIX IPC提供了一套独立于系统的进程间通信机制,包括消息队列、共享内存、信号量、互斥量、条件变量和读写锁等。POSIX IPC接口与System V IPC接口不同,但功能相似。
4.1 POSIX 消息队列
POSIX消息队列类似于System V消息队列,但接口不同。POSIX消息队列提供了更灵活的消息传递机制,支持多种消息类型和优先级。
POSIX消息队列的基本操作
POSIX消息队列的基本操作包括创建消息队列、发送消息和接收消息。
-
创建消息队列:
c#include <mqueue.h> mqd_t mq_open(const char *name, int oflag, ...);
-
发送消息:
c#include <mqueue.h> int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
-
接收消息:
c#include <mqueue.h> ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);
POSIX消息队列的使用示例
以下示例展示了如何使用POSIX消息队列进行进程间通信:
创建和发送消息:
c
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
mqd_t mq;
struct mq_attr attr;
char *msg = "Hello, POSIX Message Queue!";
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 256;
attr.mq_curmsgs = 0;
if ((mq = mq_open("/testqueue", O_CREAT | O_WRONLY, 0644, &attr)) == (mqd_t)-1) {
ERR_EXIT("mq_open");
}
if (mq_send(mq, msg, strlen(msg) + 1, 0) == -1) {
ERR_EXIT("mq_send");
}
printf("Message sent: %s\n", msg);
if (mq_close(mq) == -1) {
ERR_EXIT("mq_close");
}
return 0;
}
接收消息:
c
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
mqd_t mq;
char buf[256];
struct mq_attr attr;
if ((mq = mq_open("/testqueue", O_RDONLY)) == (mqd_t)-1) {
ERR_EXIT("mq_open");
}
if (mq_getattr(mq, &attr) == -1) {
ERR_EXIT("mq_getattr");
}
if (mq_receive(mq, buf, attr.mq_msgsize, NULL) == -1) {
ERR_EXIT("mq_receive");
}
printf("Message received: %s\n", buf);
if (mq_close(mq) == -1) {
ERR_EXIT("mq_close");
}
if (mq_unlink("/testqueue") == -1) {
ERR_EXIT("mq_unlink");
}
return 0;
}
在这个示例中,两个进程通过POSIX消息队列进行通信。一个进程发送消息,另一个进程接收消息。POSIX消息队列适用于需要灵活消息传递的应用场景。
4.2 POSIX 共享内存
POSIX共享内存类似于System V共享内存,但接口不同。POSIX共享内存提供了一种高效的数据共享机制,允许多个进程访问同一块内存区域。
POSIX共享内存的基本操作
POSIX共享内存的基本操作包括创建共享内存对象、映射共享内存、解除映射共享内存和删除共享内存对象。
-
创建共享内存对象:
c#include <fcntl.h> #include <sys/mman.h> int shm_open(const char *name, int oflag, mode_t mode);
-
映射共享内存:
c#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
-
解除映射共享内存:
c#include <sys/mman.h> int munmap(void *addr, size_t length);
-
删除共享内存对象:
c#include <sys/mman.h> int shm_unlink(const char *name);
POSIX共享内存的使用示例
以下示例展示了如何使用POSIX共享内存进行进程间通信:
创建共享内存对象并写入数据:
c
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define SHM_SIZE 1024
int main() {
int fd;
char *shm_addr;
if ((fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666)) == -1) {
ERR_EXIT("shm_open");
}
if (ftruncate(fd, SHM_SIZE) == -1) {
ERR_EXIT("ftruncate");
}
if ((shm_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
ERR_EXIT("mmap");
}
strcpy(shm_addr, "Hello, POSIX Shared Memory!");
if (munmap(shm_addr, SHM_SIZE) == -1) {
ERR_EXIT("munmap");
}
if (close(fd) == -1) {
ERR_EXIT("close");
}
return 0;
}
映射共享内存并读取数据:
c
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define SHM_SIZE 1024
int main() {
int fd;
char *shm_addr;
if ((fd = shm_open("/myshm", O_RDONLY, 0666)) == -1) {
ERR_EXIT("shm_open");
}
if ((shm_addr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
ERR_EXIT("mmap");
}
printf("Data read from shared memory: %s\n", shm_addr);
if (munmap(shm_addr, SHM_SIZE) == -1) {
ERR_EXIT("munmap");
}
if (close(fd) == -1) {
ERR_EXIT("close");
}
if (shm_unlink("/myshm") == -1) {
ERR_EXIT("shm_unlink");
}
return 0;
}
在这个示例中,两个进程通过POSIX共享内存进行数据共享。一个进程将数据写入共享内存,另一个进程从共享内存读取
数据。POSIX共享内存适用于需要高效数据传输的应用场景。
4.3 POSIX 信号量
POSIX信号量类似于System V信号量,但接口不同。POSIX信号量用于进程同步和互斥,确保多个进程之间的协调操作。
POSIX信号量的基本操作
POSIX信号量的基本操作包括初始化信号量、等待信号量和释放信号量。
-
初始化信号量:
c#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned value);
-
等待信号量:
c#include <semaphore.h> int sem_wait(sem_t *sem);
-
释放信号量:
c#include <semaphore.h> int sem_post(sem_t *sem);
POSIX信号量的使用示例
以下示例展示了如何使用POSIX信号量进行进程同步:
初始化信号量并进行同步操作:
c
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
sem_t sem;
void *worker(void *arg) {
sem_wait(&sem);
printf("Worker thread: entering critical section.\n");
sleep(2);
printf("Worker thread: leaving critical section.\n");
sem_post(&sem);
return NULL;
}
int main() {
pthread_t tid;
if (sem_init(&sem, 0, 1) == -1) {
ERR_EXIT("sem_init");
}
if (pthread_create(&tid, NULL, worker, NULL) != 0) {
ERR_EXIT("pthread_create");
}
sem_wait(&sem);
printf("Main thread: entering critical section.\n");
sleep(2);
printf("Main thread: leaving critical section.\n");
sem_post(&sem);
pthread_join(tid, NULL);
if (sem_destroy(&sem) == -1) {
ERR_EXIT("sem_destroy");
}
return 0;
}
在这个示例中,主线程和工作线程通过信号量实现同步,确保在同一时间只有一个线程进入临界区。POSIX信号量适用于需要线程同步和互斥的应用场景。
4.4 互斥量和条件变量
互斥量和条件变量是用于线程同步的高级机制。互斥量用于实现线程之间的互斥,条件变量用于实现线程之间的条件等待。
互斥量的基本操作
互斥量的基本操作包括初始化互斥量、加锁互斥量和解锁互斥量。
-
初始化互斥量:
c#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
-
加锁互斥量:
c#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);
-
解锁互斥量:
c#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
条件变量的基本操作
条件变量的基本操作包括初始化条件变量、等待条件变量和发信号条件变量。
-
初始化条件变量:
c#include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
-
等待条件变量:
c#include <pthread.h> int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
-
发信号条件变量:
c#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond);
互斥量和条件变量的使用示例
以下示例展示了如何使用互斥量和条件变量进行线程同步:
使用互斥量和条件变量实现生产者-消费者模型:
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
pthread_mutex_t mutex;
pthread_cond_t cond;
int buffer = 0;
int buffer_full = 0;
void *producer(void *arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (buffer_full) {
pthread_cond_wait(&cond, &mutex);
}
buffer = rand() % 100;
printf("Produced: %d\n", buffer);
buffer_full = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
void *consumer(void *arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (!buffer_full) {
pthread_cond_wait(&cond, &mutex);
}
printf("Consumed: %d\n", buffer);
buffer_full = 0;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
if (pthread_mutex_init(&mutex, NULL) != 0) {
ERR_EXIT("pthread_mutex_init");
}
if (pthread_cond_init(&cond, NULL) != 0) {
ERR_EXIT("pthread_cond_init");
}
if (pthread_create(&tid1, NULL, producer, NULL) != 0) {
ERR_EXIT("pthread_create");
}
if (pthread_create(&tid2, NULL, consumer, NULL) != 0) {
ERR_EXIT("pthread_create");
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
if (pthread_mutex_destroy(&mutex) != 0) {
ERR_EXIT("pthread_mutex_destroy");
}
if (pthread_cond_destroy(&cond) != 0) {
ERR_EXIT("pthread_cond_destroy");
}
return 0;
}
在这个示例中,生产者线程和消费者线程通过互斥量和条件变量进行同步,实现生产者-消费者模型。互斥量和条件变量适用于需要线程间复杂同步的应用场景。
5. 总结
进程间通信是现代操作系统中至关重要的一部分,理解和掌握各种进程间通信机制可以帮助开发高效、可靠的多进程和多线程应用程序。
本文详细介绍了进程间通信的各种方式,包括管道、消息队列、共享内存和信号量等,通过具体的实例代码展示了它们的使用方法和应用场景。
希望通过本文的学习,你能更加深入地理解和掌握进程间通信,为开发高效、可靠的程序打下坚实的基础。
嗯,就是这样啦,文章到这里就结束啦,真心感谢你花时间来读。
觉得有点收获的话,不妨给我点个赞吧!
如果发现文章有啥漏洞或错误的地方,欢迎私信我或者在评论里提醒一声~