lesson6:进程间通信

1. 进程间通信

1.1 基本概念

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息

  • 应用:a.数据传输,b.资源共享,c.通知事件,d.进程控制
  • 本质:先让不同的进程看到同一份资源(内存空间)

1.2 技术背景

  1.  进程是具有独立性的,虚拟地址空间+页表,
    保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
  2.  通信成本会比较高

1.3 本质理解

  1. 进程间通信的前提,首先需要让不同的进程看到同一块"内存"(特定的结构组织的)
  2. 所以你所谓的进程看到同一块"内存",它不能隶属于任何一个进程,而应该更加强调共享

1.4 通信方式

  1. linux原生能提供 - 管道
    1. 匿名管道
    2. 命名管道
  2. SystemV --- 多进程 -- 单机通信
    1. 共享内存
    2. 消息队列(不常用)
    3. 信号量(理解原理即可)
  3. posix -- 多线程 -- 网络通信

2. 管道

一种单向通信的方式,有入口,有出口,传输的都是资源

 如何做的让不同的进程看到同一份资源?

  • fork让子进程继承的,能够让具有血缘关系的进程进行进程间通信,常常用于父子进程

2.1 案例: 模拟实现匿名管道

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。 

 

 

 

数组元素                                       含义
pipefd[0]                          管道读端的文件描述符
pipefd[1]                           管道写端的文件描述符
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

// 为什么不定义全局buffer来进行通信呢?? 因为有写时拷贝的存在,无法更改通信!

int main()
{
    // 1. 创建管道
    int pipefd[2] = {0};// 父子进程都能看到
    int n = pipe(pipefd);
    assert(n != -1); // debug assert, release assert
    (void)n;

#ifdef DEBUG
    cout << "pipefd[0]: " << pipefd[0] << endl; // 3
    cout << "pipefd[1]: " << pipefd[1] << endl; // 4
#endif
    // 2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        //子进程 - 读
        // 3. 构建单向通信的信道,父进程写入,子进程读取
        // 3.1 关闭子进程不需要的fd
        close(pipefd[1]);// 关闭写端
        char buffer[1024 * 8];
        while (true)
        {
            // sleep(20);
            // 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等
            // 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!
            ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0;
                cout << "child get a message[" << getpid() << "] Father# " << buffer << endl;
            }
            else if(s == 0)
            {
                cout << "writer quit(father), me quit!!!" << endl;
                break;
            }
        }
        // close(pipefd[0]);
        exit(0);
    }
    //父进程 - 写
    // 3. 构建单向通信的信道
    // 3.1 关闭父进程不需要的fd
    close(pipefd[0]);// 关闭读端
    string message = "我是父进程,我正在给你发消息";
    int count = 0;
    char send_buffer[1024 * 8];
    while (true)
    {
        // 3.2 构建一个变化的字符串
        snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",
                 message.c_str(), getpid(), count++);
        // 3.3 写入
        write(pipefd[1], send_buffer, strlen(send_buffer));
        // 3.4 故意sleep
        sleep(1);
        cout << count << endl;
        if (count == 5){
            cout << "writer quit(father)" << endl;
            break;
        }
    }
    close(pipefd[1]);
    pid_t ret = waitpid(id, nullptr, 0);
    cout << "id : " << id << " ret: " << ret <<endl;
    assert(ret > 0); 
    (void)ret;

    return 0;
}

  • 写快,读慢  写满就不能在写了
  • 写慢, 读快, 管道没有数据的时候,读就必须等待
  • 写关, 读0, 标识读到了文件结尾
  • 读关, 写继续写, OS终止写进程

2.2 案例: 模拟实现命名管道 

  

2.2.1 comm.hpp 

#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define MODE 0666 // 初始权限
#define SIZE 128 // 空间大小

// server 和 client看到的同一个文件
string ipcPath = "./fifo.ipc";

#endif

2.2.2 Log.hpp

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3


const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}

#endif

  2.2.3 server.cpp 

#include "comm.hpp"
#include <sys/wait.h>

static void getMessage(int fd)
{
    char buffer[SIZE];
    while (true)
    {
        memset(buffer, '\0', sizeof(buffer));// 清空
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            // success
            cout <<"["  << getpid() << "] "<< "client say> " << buffer << endl;
        }
        else if (s == 0)
        {
            // end of file
            cerr <<"["  << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;
            break;
        }
        else
        {
            // read error
            perror("read");
            break;
        }
    }
}

int main()
{
    // 1. 创建管道文件
    if (mkfifo(ipcPath.c_str(), MODE) < 0)
    {
        perror("mkfifo");
        exit(1);
    }
    Log("创建管道文件成功", Debug) << " step 1" << endl;

    // 2. 正常的文件操作
    int fd = open(ipcPath.c_str(), O_RDONLY);// O_RDONLY 以只读
    if (fd < 0)
    {
        perror("open");
        exit(2);
    }
    Log("打开管道文件成功", Debug) << " step 2" << endl;

    // 创建3个子进程
    int nums = 3;
    for (int i = 0; i < nums; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // 3. 编写正常的通信代码了
            getMessage(fd);
            exit(1);
        }
    }
    for(int i = 0; i < nums; i++)
    {
        waitpid(-1, nullptr, 0);
    }
    // 4. 关闭文件
    close(fd);
    Log("关闭管道文件成功", Debug) << " step 3" << endl;

    unlink(ipcPath.c_str()); // 通信完毕,就删除文件
    Log("删除管道文件成功", Debug) << " step 4" << endl;

    return 0;
}

 2.2.4 client.cxx

#include "comm.hpp"

int main()
{
    // 不用再创建命名管道了
    // 1. 获取管道文件
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }

    // 2. ipc过程
    string buffer;
    while(true)
    {
        cout << "Please Enter Message Line :> ";
        std::getline(std::cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    // 3. 关闭
    close(fd);
    return 0;
}

2.2.5 Makefile

2.3 总结管道的特点

  • 管道内部自带同步与互斥机制
  • 管道的生命周期随进程
  • 管道提供的是流式服务
  • 管道是半双工通信

 在数据通信中,数据在线路上的传送方式可以分为以下三种:

  • 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
  • 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
  • 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。

2.4 管道的四种特殊情况 

  1. 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
  2. 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
  3. 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
  4. 读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉(发送信号??)

子进程退出时究竟是收到了什么信号 

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
	return 0;
}

 

  •  运行结果显示,子进程退出时收到的是13号信号->SIGPIPE

2.6 管道大小 

方法一:使用man手册

根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面大小相同,从Linux 2.6.11往后,管道的最大容量是65536字节 

 然后我们可以使用uname -r命令,查看自己使用的Linux版本。

根据man手册,我使用的是Linux 2.6.11之后的版本,因此管道的最大容量是65536字节。 

 方法二:使用ulimit命令

其次,我们还可以使用ulimit -a命令,查看当前资源限制的设定 

根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096512×8=4096 字节

方法三:自行测试

读端进程一直不读取管道当中的数据,写端进程一直向管道写入数据,当管道被写满后,写端进程就会被挂起。据此,我们可以写出以下代码来测试管道的最大容量 

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child 
		close(fd[0]); //子进程关闭读端
		char c = 'a';
		int count = 0;
		//子进程一直进行写入,一次写入一个字节
		while (1){
			write(fd[1], &c, 1);
			count++;
			printf("%d\n", count); //打印当前写入的字节数
		}
		close(fd[1]);
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端

	//父进程不进行读取

	waitpid(id, NULL, 0);
	close(fd[0]);
	return 0;
}

 

  •  可以看到,在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前Linux版本中管道的最大容量是65536字节

 

3. 共享内存

  •  共享内存的提供者是操作系统,操作系统也要管理共享内存
  • 先描述再组织,共享内存 = 共享内存块 + 对应的共享内存的内核数据结构!

 

3.1  comm.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include "Log.hpp"

using namespace std; 

#define PATH_NAME "/home/lyc" // 唯一路径
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍 

3.2 Log.hpp

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3

const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}

#endif

3.3 shmServer.cc 

#include "comm.hpp"

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;
    sleep(10);
    
    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;
    sleep(10);

    // 这里就是通信的逻辑了


    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;
    sleep(10);

    // 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;
    return 0;
}

 3.4 shmClient.cc

#include "comm.hpp"

int main()
{
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0){
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 2. 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid < 0){
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;
    sleep(10);

    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if(shmaddr == nullptr){
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;
    sleep(10);

    // 这里就是使用的逻辑了

    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;
    sleep(10);

    // client不需要chmctl删除共享内存
    return 0;
}

 

  • while :; do ipcs -m | head -1 && ipcs -m | grep -v grep; sleep 1;done

4. 共享内存 + 管道

  •  共享内存是属于用户空间的,不用经过系统调用,直接可以访问
  • 双方进程如果要通信,直接进行内存级的读和写即可

4.1 comm.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std; //不推荐

#define PATH_NAME "/home/lyc"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍

#define FIFO_NAME "./fifo"

class Init
{
public:
    Init()
    {
        // 创建管道
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success",Notice) << "\n";
    }
    ~Init()
    {
        unlink(FIFO_NAME);
        Log("remove fifo success",Notice) << "\n";
    }
};

#define READ O_RDONLY // 只读
#define WRITE O_WRONLY // 只写

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    Log("等待中....", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中....", Notice) << "\n";
}

void CloseFifo(int fd)
{
    close(fd);
}

4.2 Log.hpp

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3


const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}


#endif

4.3 shmServer.cc 

#include "comm.hpp"

// 是不是对应的程序,在加载的时候,会自动构建全局变量,就要调用该类的构造函数 -- 创建管道文件
// 程序退出的时候,全局变量会被析构,自动调用析构函数,会自动删除管道文件
Init init; 

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 我们之前为了通信,所做的所有的工作,属于什么工作呢:让不同的进程看到了同一份资源(内存)
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);

    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;

    // sleep(10);
    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;

    // sleep(10);

    // 这里就是通信的逻辑了
    // 将共享内存当成一个大字符串
    // char buffer[SHM_SIZE];
    // 结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据。
    //         共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)
    // 结论2: 共享内存缺乏访问控制!会带来并发问题 【如果我想一定程度的访问控制呢? 能】
    
    int fd = OpenFIFO(FIFO_NAME, READ);
    for(;;)
    {
        Wait(fd);

        // 临界区
        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        // sleep(1);
    }
    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;
    // sleep(10);

    // 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;

    CloseFifo(fd);
    return 0;
}

4.4 shmClient.cc

#include "comm.hpp"

int main()
{
    Log("child pid is : ", Debug) << getpid() << endl;
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;

    // sleep(10);

    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;
    // sleep(10);

    int fd = OpenFIFO(FIFO_NAME, WRITE);
    // 使用
    // client将共享内存看做一个char 类型的buffer
    while(true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE-1);
        if(s > 0)
        {
            shmaddr[s-1] = 0;
            Signal(fd);
            if(strcmp(shmaddr,"quit") == 0) break;
        }
    }

    CloseFifo(fd);

    // 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;
    // sleep(10);

    // client 要不要chmctl删除呢?不需要!!

    return 0;
}

4.5 Makefile 

  •  结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!(不需要将数据给操作系统)
  • 结论2: 共享内存缺乏访问控制!会带来并发问题

    • 通过管道可以实现一定程度的访问控制, 

5. 理解共享内存是速度最快的

从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作: 

  •  服务端将信息从输入文件复制到服务端的临时缓冲区中。
  • 将服务端临时缓冲区的信息复制到管道中。
  • 客户端将信息从管道复制到客户端的缓冲区中。
  • 将客户端临时缓冲区的信息复制到输出文件中。

从这张图可以看出,使用共享内存进行通信,将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作 

  • 从输入文件到共享内存
  • 从共享内存到输出文件

所以共享内存是所有进程间通信方式中最快的一种通信方式,因为该通信方式需要进行的拷贝次数最少。

但是共享内存也是有缺点的,我们知道管道是自带同步与互斥机制的,但是共享内存并没有提供任何的保护机制,包括同步与互斥

6. 基于对共享内存的理解

进程之间的通信,关键在于让不同的进程看到同一份资源,比如共享内存,

但是这样做这样做,也会带来一些时序问题,造成了数据不一致问题!!

  1. 我们把多个进程(执行流)看到的公共的一份资源 -- 临界资源
  2. 我们把自己的代码,访问临界资源的代码 -- 临界区
  3. 为了更好地对临界区的保护
    可以让多执行流,在任何时刻,都只能有一个进程进入临界区 -- 互斥
  4. 原子性: 要么不做,要么做完,没有中间状态,就称之为原子性 

所以,多个执行流,互相运行的时候互相干扰,主要是我们不加保护的访问了同样的资源(临界资源),
在非临界区,多个执行流互相是不影响的!! 

7. 信号量的引入

生活小例子:

  • 看电影一定要有座位(放映厅里面的一个资源),这个座位真正属于你,不是你自己坐在这个位置上才属于你,而是先买票,只要我买了票,我就拥有了这个座位,
    • 买票的本质: 对座位的预订机制

 每一个进程想进入临界资源,访问临界资源中的一部分,

  • 不能让进程直接去使用临界资源(就像不能让用户直接去电影院内部占座位),
  • 首先需要先申请信号量(就像你需要先买票)

 信号量的本质就是一个计数器,类似 int count = n;(但是不准确)

  • 申请信号量的本质:让信号量计数器--
  • 如果申请信号量成功,那么临界资源内部,一定给你预留了你想要的资源
    • 申请信号量本质其实是对临界资源的一种预订机制
    • 申请信号量 --
    • 访问临界资源 -- > 进程执行自己的临界区代码
    • 释放信号量 ++

7.1 如何标识信号量

虽然信号量是一个计数器,但是也不能使用int n = 10; 用一个整数去标识信号量(error)

假设让多个进程(整数n在共享内存里),看到同一个全局变量,大家都进行申请信号量(error)

信号量计数器是对临界资源的预订机制!!

  • 申请信号量,计数器--,P操作 -> 必须是原子的
  • 释放信号量,计数器++,V操作-> 必须是原子的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值