进程通信
进程同步与进程通信很容易混淆,它们的区别在于:
- 进程同步:控制多个进程按一定顺序执行;
- 进程通信:进程间传输信息。
进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。
1. 管道
管道是通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写。
#include <unistd.h>
int pipe(int fd[2]);
它具有以下限制:
- 只支持半双工通信(单向交替传输);
- 只能在父子进程中使用。
例子:
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0]
)与子进程的写端(fd[1]
);反之,则可以使数据流从子进程流向父进程。
#include<stdio.h>
#include<unistd.h>
int main() {
int fd[2];
pid_t pid;
char buff[20];
if(pipe(fd) < 0) //创建管道
printf("Create Pipe error!\n");
if(pid = fork() < 0) //创建子进程
printf("Fork error!\n");
else if(pid > 0) { //父进程
close(fd[0]); //关闭读端
write(fd[1], "hello world\n", 12);
}
else {
close(fd[1]); //关闭写端
read(fd[0], buff, 20);
printf("%s", buff);
}
return 0;
}
2. FIFO
也称为命名管道,去除了管道只能在父子进程中使用的限制。
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
3. 消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
相比于 FIFO,消息队列具有以下优点:
- 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
4. 信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
此处只涉及利用“二进制信号量”(只用0和1)完成“控制线程顺序”为中心的同步方法。
信号量的创建和销毁函数:
信号量相当于互斥量lock,unlock的函数:
sem_wait(&sem); //信号量变为0...
//临界区的开始
//.........
//临界区的结束
sem_post(&sem); //信号量变为1
上述代码中,调用sem_wait函数进入临界区的线程在调用sem_post函数前不允许其它线程进入临界区。
信号量的值在0和1之间跳转,因此,具有这种特性的机制称为”二进制信号量“。
示例:线程A从输入得到值后存入全局变量num,此时线程B将取走该值并累加。该过程共进行5次,完成后输出总和并退出
/* 控制访问顺序的线程同步 */
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
void * read(void * arg);
void * accu(void * arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;
int main(int argc, char *argv[])
{
pthread_t id_t1,id_t2;
sem_init(&sem_one,0,0); //sem_one初始值为0
sem_init(&sem_two,0,1); //sem_two初始值为1
pthread_create(&id_t1,NULL,read,NULL);
pthread_create(&id_t2,NULL,accu,NULL);
pthread_join(id_t1,NULL);
pthread_join(id_t2,NULL);
sem_destroy(&sem_one);
sem_destroy(&sem_two);
return 0;
}
void * read(void * arg)
{
int i;
for(i=0; i<5; i++)
{
fputs("Input num: ",stdout);
sem_wait(&sem_two); //sem_two变为0,阻塞,在accu中加1后跳出阻塞状态
scanf("%d",&num);
sem_post(&sem_one); //sem_one变为1
}
return NULL;
}
void * accu(void * arg)
{
int sum=0, i;
for(i=0; i<5; i++)
{
sem_wait(&sem_one); //sem_one变为0,阻塞,在read中加1后跳出阻塞状态
sum+=num;
sem_post(&sem_two); //sem_two变为1
}
printf("Result: %d \n",sum);
return NULL;
}
运行结果:
5. 共享存储
允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。
需要使用信号量用来同步对共享存储的访问。
多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用使用内存的匿名段。
6. 套接字
与其它通信机制不同的是,它可用于不同机器间的进程通信。