操作系统之进程管理:进程通信

进程通信

进程同步与进程通信很容易混淆,它们的区别在于:

  • 进程同步:控制多个进程按一定顺序执行;
  • 进程通信:进程间传输信息。

进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。

 

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

也称为命名管道

  • 有名管道是FIFO文件,存在于文件系统中,可以通过文件路径名来指出。
  • 有名管道可以在不具有亲缘关系的进程间进行通信。
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
  • pathname:即将创建的FIFO文件路径,如果文件存在需要先删除。

  • mode:和open()中的参数相同。

FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。

             

 

3. 消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

相比于 FIFO,消息队列具有以下优点:

  • 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
  • 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
  • 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。

 

消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。

主要的使用场景就是将比较耗时而且不需要立即生效返回结果的操作,我们把这种操作作为一个消息,放到消息队列中。处理方可以在任何时候去获取并处理这条消息。这里我们只要保证消息的格式不变,消息的发送方和接收处理方都认识这个消息,那么双方就不需要彼此通信,即可以完成一件事。

 

当然,如果我们使用消息队列的话,也有许多需要注意的点。比如,消息的发送方不需要接收方立即返回处理结果,否则的话只能等待处理结果;比如系统会有短暂的不一致性,发送方不可预知接收方什么时间处理完这个消息。当然,实际生产中还是有需要地方允许这些比如的,所以消息队列现在是异常的火爆。

 

下面举一个网上的例子(自我感觉,非常的生动形象):

假设用户在软件中注册,服务端收到用户的注册请求后,它会做这些操作:

  • 校验用户名等信息,如果没问题会在数据库中添加一个用户记录
  • 如果是用邮箱注册会给你发送一封注册成功的邮件
  • 手机注册则会发送一条短信
  • 分析用户的个人信息以便将来向他推荐一些志同道合的人,或向那些人推荐他
  • 发送给用户一个包含操作指南的系统通知等等……

 

但是对于用户来说,注册功能实际只需要第一步,只要服务端将他的账户信息存到数据库中他便可以登录上去做他想做的事情了。至于其他的事情,服务端就可以把其他的操作放入对应的消息队列中然后马上返回用户结果,由消息队列异步的进行这些操作。

 

4. 信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。多线程同步的信号量是POSIX信号量, 而在进程里使用SYSTEM V信号量。

 

此处只涉及利用“二进制信号量”(只用0和1)完成“控制线程顺序”为中心的同步方法。

信号量的创建和销毁函数:

信号量相当于互斥量lock,unlock的函数:

  1. sem_wait(&sem); //信号量变为0...

  2. //临界区的开始

  3. //.........

  4. //临界区的结束

  5. 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

相关接口

  • 创建共享内存:int shmget(key_t key, int size, int flag);

    成功时返回一个和key相关的共享内存标识符,失败范湖范围-1。

    • key:为共享内存段命名,多个共享同一片内存的进程使用同一个key。

    • size:共享内存容量。

    • flag:权限标志位,和open的mode参数一样。

  • 连接到共享内存地址空间:void *shmat(int shmid, void *addr, int flag);

    返回值即共享内存实际地址。

    • shmid:shmget()返回的标识。

    • addr:决定以什么方式连接地址。

    • flag:访问模式。

  • 从共享内存分离:int shmdt(const void *shmaddr);

    调用成功返回0,失败返回-1。

    • shmaddr:是shmat()返回的地址指针。

 

共享内存的方式像极了多线程中线程对全局变量的访问,大家都对等地有权去修改这块内存的值,这就导致在多进程并发下,最终结果是不可预期的。所以对这块临界区的访问需要通过信号量来进行进程同步。

共享内存的优势也很明显,首先可以通过共享内存进行通信的进程不需要像无名管道一样需要通信的进程间有亲缘关系。其次内存共享的速度也比较快,不存在读取文件、消息传递等过程,只需要到相应映射到的内存地址直接读写数据即可。

 

6. 套接字

与其它通信机制不同的是,它可用于不同机器间的进程通信。

 

 

 

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值