2、GO语言多进程编程

  • 一 进程概念
  • 二 进程创建
  • 四 进程分类
  • 五 进程调度
  • 六 进程状态转换
  • 七 进程运行的问题
    • 7.1 写时复制
    • 7.2 进程回收
  • 八 进程间通信
    • 8.1 进程间通信方式概述
    • 8.1 管道
    • 8.2 内存映射区
    • 8.3 信号
    • 8.4 socket
    • 九 进程同步

一 进程概念

进程:就是二进制可执行文件在计算机内存中的运行实例,可以简单理解为:一个.exe文件是个类,进程就是该类new出来的实例。

进程是操作系统最小的资源分配单位(如虚拟内存资源),所有代码都是在进程中执行的

为了方便管理进程,每个进程都有自己的描述符,是个复杂的数据结构,我们称之为进程控制块,即PCB(Process Control Block)。

PCB中保存了进程的管理、控制信息等数据,主要包含字段有:

  • 进程ID(PID):进程的唯一标识符 ,是一个非负整数的顺序编号
  • 父进程ID(PPID):当前进程的父进程ID
  • 文件描述符表:即很多指向file接否提的指针
  • 进程状态:就绪、运行、挂起、停止等状态
  • 虚拟地址范围
  • 访问权限
  • 当前工作目录
  • 用户id和组id
  • 会话和进程组

贴士:进程ID是可以重用的,当进程ID达到最大限额值时,内核会从头开始查找闲置的进程ID并使用最先找到的那一个作为新进程的ID

二 进程创建

Unix系统在启动后,会首先运行一个名为 init 的进程,其PID 为 1。该进程是所有其他进程的父进程。

Unix操作系统通过 fork() 函数能够创建多个子进程,从而能够提升计算机资源的利用率。此时调用者称为父进程,被创造出来的进程称为子进程。

  • 每个子进程都是源自它的父进程的一个副本,它会获得父进程的数据段、堆、栈的拷贝,并与父进程共享代码段。
  • 子进程对自己副本的修改对其父进程和兄弟进程都是不可见的,反之亦然。

创建的子进程可以直接开始运行,但是也可以通过 exec() 函数来加载一个全新的程序,此时子进程会丢弃现存的程序文本段,为加载的新程序重新创建栈、数据段、堆,我们对这一个过程称为执行一个新程序。

贴士:exec并不是1个函数, 是一系列 exec 开头的函数,作用都是执行新程序。

C语言示例如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(){pid_t pid;int r;// 创建子进程pid = fork();                     if (pid == -1){                   // 发生错误perror("fork发生错误 ");exit(1);}// 返回值大于0时是父进程if(pid > 0){                        printf("父进程: pid = %d, ppid = %d \n", getpid(),getppid());        // 父进程执行动作sleep(3);                       // 父进程睡眠,防止子进程还没运行完毕,父进程却直接退出了}// 返回值为0的是子进程if(pid == 0){   printf("子进程: pid = %d , ppid = %d \n", getpid(),getppid());     // 子进程执行动作// 子进程加载一个新程序:系统自带的 echo程序,输出 hello world!char * execv_str[] = {"echo", "hello world!",NULL};int r = execv("/bin/echo", execv_str);    // 笔者的是mac,linux上为: "/usr/bin/echo"  if (r <0 ){perror("error on exec");exit(0);}}return 0;
}

在 Go 语言中,没有直接提供 fork 系统调用的封装,而是将 fork 和 execve 合二为一,具体信息可以参见Go的os包。

package mainimport ("fmt""os""time"
)func main() {fmt.Println("当前进程ID:", os.Getpid())procAttr := &os.ProcAttr{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},}process, err := os.StartProcess("/bin/echo", []string{"", "hello,world!"}, procAttr)if err != nil {fmt.Println("进程启动失败:", err)os.Exit(2)} else {fmt.Println("子进程ID:", process.Pid)}time.Sleep(time.Second)}

根据该方式,就可以很容运行计算机上的其他任何程序,包括自身的命令行、Java程序等等。

四 进程分类

进程分类

  • 用户进程:位于用户空间中,是程序执行的实例
  • 内核进程:位于内核空间中,可以访问硬件

由于用户进程无法访问内核空间,所以无法直接操作硬件。内核会暴露一些接口提供给用户进程使用,让用户进程简介操作硬件,这便是系统调用。

内核为了保证系统的安全和稳定,为CPU特供了两个状态:

  • 用户态:大部分时间CPU处于该状态,此时只能访问用户空间
  • 内核态:当用户进程发起系统调用时,内核会将CPU切换到内核态,然后执行相应接口函数。

注意:这里的用户态和内核态是针对CPU的。

五 进程调度

同一时刻只能运行一个进程,但是CPU可以在多个进程间进行来回切换,我们称之为上下文切换。

操作系统会按照调度算法为每个进程分配一定的CPU运行时间,称之为时间轮片,每个进程在运行时都会认为自己独占了CP。

切换进程是有代价的,因为必须保存进程的运行时状态。

六 进程状态转换

进程在创建后,在执行过程中,其状态一直在变化。不同时代的操作系统有不同的进程模型:

  • 三态模型:运行态、就绪态、等待态
  • 五态模型:初始态、就绪态、运行态、挂起态(阻塞)、终止态

七 进程运行的问题

7.1 写时复制

父进程无法预测子进程什么时候结束,只有进程完成工作后,父进程才会调用子进程的终止态。

贴士:全盘复制父进程的数据相当低效,Linux使用写时复制(COW:Copy on Write)技术来提高进程的创建效率。

7.2 进程回收

当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。

孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,此时该进程会被系统的 init 进程领养

僵尸进程:子进程终止,但父进程未回收,子进程残留资源(PCB)于内核中,变成僵尸进程。

注意:由于僵尸进程是一个已经死亡的进程,所以不能使用kill命令将其杀死,通过杀死其父进程的方法可以消除僵尸进程,杀死其父进程后,这个僵尸进程会被init进程领养,由init进程完成对僵尸进程的回收。

八 进程间通信

8.1 进程间通信方式概述

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

  • 管道 (使用最简单)
  • 共享映射区 (无血缘关系进程通信)
  • 信号 (开销最小)
  • 本地套接字 (最稳定)

Go支持的IPC方法有:管道、信号、socket。

8.1 管道

管道是一种最基本的IPC机制,也称匿名管道,应用于有血缘关系的进程之间,完成数据传递。调用C的pipe函数即可创建一个管道。

管道有如下特质:

  • 管道的本质是一块内核缓冲区
  • 由两个文件描述符引用,一个表示读端,一个表示写端。
  • 规定数据从管道的写端流入管道,从读端流出。
  • 当两个进程都终结的时候,管道也自动消失。
  • 管道的读端和写端默认都是阻塞的。

管道的实质是内核缓冲区,内部使用唤醒队列实现。

管道的缺陷:

  • 管道中的数据一旦被读走,便不在管道中存在,不可反复读取。
  • 数据只能在一个方向上流动,若要实现双向流动,必须使用两个管道
  • 只能在有血缘关系的进程间使用管道。

Go模拟管道的实现:

 cmd1 := exec.Command("ps", "aux")cmd2 := exec.Command("grep", "apipe")var outputBuf1 bytes.Buffercmd1.Stdout = &outputBuf1cmd1.Start()cmd1.Wait()                // 开始阻塞var outputBuf2 bytes.Buffercmd2.Stdout = &outputBuf2cmd2.Start()cmd2.Wait()             // 开始阻塞fmt.Println(outputBuf2.Bytes())

当然也有一种管道称为命名管道(FIFO),它支持无血缘关系的进程之间通信。FIFO是Linux基础文件类型中的一种(文件类型为p,可通过ls -l查看文件类型)。但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来标识内核中一条通道。进程可以打开这个文件进行read/write,实际上是在读写内核缓冲区,这样就实现了进程间通信。

8.2 内存映射区

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。从缓冲区中取数据,就相当于读文件中的相应字节;将数据写入缓冲区,则会将数据写入文件。这样,就可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。

使用存储映射这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

8.3 信号

信号是IPC中唯一一种异步的通信方法,本质是用软件模拟硬件的中断机制,例如:在命令行终端按下某些快捷键,就会挂起或停止正在运行的程序。Go中的ginal包提供了相关操作。

 sigRecv := make(chan os.Signal, 1)                      // 创建接收通道sigs := []os.Signal{syscall.SIGINT, syscall.SIGQUIT}    // 创建信号类型signal.Notify(sigRecv, sigs...)for sig := range sigRecv {                              // 循环接收通道中的信号,通道关闭后,for会立即停止fmt.Println(sig)}

8.4 socket

socket即套接字,也是一种IPC方法,与其他IPC方法不同之处在于:可以通过网络连接让多个进程建立通信并相互传递数据,这使得通信不再依赖于在同一台计算机上。

九 进程同步

当多个子进程对同一资源进行访问时,就会产生竞态条件。比如:某一个数据,进程A对其进行执行一系列操作,但是在执行过程中,系统有可能会切换到另外一个进程B中,B也对该数据进行一系列操作,那么在两个进程中操作同一份数据时,这个数据的结果值到底按照谁的来运算呢?

原子操作:如果执行过程中操作不能中断,那么就能解决上述问题,这样的操作称为原子操作(atomic operation)。这些只能被串行化访问或执行的资源或者某段代码被称为临界区(critical section)。Go中(sync/atomic包提供了原子操作函数)。

注意:

  • 所有的系统调用都是原子操作,即不用担心它们的执行被中断!
  • 原子操作不能被中断,临界区是否可以被中断没有强制规定,只是保证了只能同时被一个访问者访问。

问题:如果一个原子操作无法结束,现在也无法中断,如何处理?

答案:内核只提供了针对二进制位和整数的原子操作(即保证细粒度),不会有上述现象。

互斥锁
在实际开发中,原子操作并不通用,我们可以保证只有一个进程/线程在临界区,该做法称为互斥锁(exclusion principle),比如信号量是实现互斥方法的方式之一,Golang的sync包也有对互斥的支持。

2、GO语言多进程编程相关推荐

  1. c语言多进程编程,C语言中的多进程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 序言 为了充分利用计算机中的多核CPU,计算机提供了两个接口使用多核CPU,两个接口分别是:多进程.多线程.本编文章将介 ...

  2. LINUX 多进程编程 C语言实例

    LINUX多进程编程 简单实例 1.ps与top命令 查看进程状态 2.系统调用ping,并执行 #include <stdio.h> #include <string.h> ...

  3. C语言网络编程:多路IO select实现多客户端

    文章目录 阻塞式的服务器程序 多线程服务器程序 非阻塞式服务器程序 基于事件响应的服务器程序 事件响应服务器程序的实现`select` 阻塞式的服务器程序 我们接触过最多的最基础的网络通信模型为TCP ...

  4. 服务器端PHP多进程编程实战

    服务器端PHP多进程编程实战 http://developer.51cto.com  2010-10-15 08:57  Bruce Dou  博客  我要评论(0) PHP是目前应用最广泛的Web开 ...

  5. Unix 多进程编程

    一.多进程程序的特点 由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在 实质上应该说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境 以使得在CPU做进程切换 ...

  6. socket多进程编程

    socket多进程编程 一.服务器并发访问的问题 服务器按处理方式可以分为迭代服务器和并发服务器两类.平常用C写的简单Socket客户端服务器通信,服务器每次只能处理一个客户的请求,它实现简单但效率很 ...

  7. 并发编程含义比较广泛,包含多线程编程、多进程编程及分布式程序等 目录 1. “共享内存系统”,消息传递系统”。 1 1.1. 共享模式 多进程 多线程 1 1.2. Actor消息模式 事件驱动 2

    并发编程含义比较广泛,包含多线程编程.多进程编程及分布式程序等 目录 1. "共享内存系统",消息传递系统". 1 1.1. 共享模式 多进程 多线程 1 1.2. Ac ...

  8. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  9. 并发编程之多进程编程(python版)

    目录 1 python多进程编程概述 2 需求和方案 背景: 需求: 解决思路: 需要解决的问题和方案: 3 完整代码 1 python多进程编程概述 python中的多线程无法利用多核优势,如果想要 ...

最新文章

  1. 记那一次-----环环相抱何是了?
  2. Android之自定义view引用xml,Android自定义View在XML中映射错误
  3. OpenCASCADE:可视化之基础概念
  4. mysql node null_node-mysql中防止SQL注入的方法总结
  5. Java中关于String类型的10个问题
  6. JBoss模块很糟糕,无法在JBoss 7下使用自定义Resteasy / JAX-RS
  7. mysql索引 红黑树_为什么MySql索引使用B+树?
  8. Scala 专题指南
  9. wordpress向一个页面POST数据,出现404页面访问不了
  10. Atitti.软件的一些理论补充 Atitti.软件的原理原则定律法则补充 目录 1.1. 分布式领域CAP理论, 1 1.2. 关系数据库的ACID模型拥有 高一致性 + 可用性 很难进行分区:
  11. 通过C语言编程一个小游戏(乐趣无边)
  12. 五子棋软件测试自学,初学者如何从零开始自学五子棋
  13. 计算机操作系统——程序执行的流程
  14. 计算机中的微信无法启动,微信电脑版无法直接打开EXCEL:为什么电脑打不开excel表格...
  15. 三十岁左右的你,现在收入多少?
  16. java程序员 .net 程序员_Java 程序员 和 .NET 程序员
  17. Hacked Exam-Google Codejam 2021 Round 1A
  18. 光学遥感影像的几何校正
  19. ModelSim-Altera路径找不到或者不正确的解决办法
  20. [ Java学习 ] 包语句 package等语句的汇总整理

热门文章

  1. URLEncoder与URLDecoder编码相互转换
  2. 使用数据库进行用户身份认证
  3. 网络攻防环境的搭建--MacOs配置Vmnet1及Vmnet8
  4. 8岁上海小学生B站教编程惊动苹果公司CEO
  5. CTF隐写(stegsolve)
  6. 使用谷歌浏览器的speechSynthesis的API,实现语音播报功能
  7. 前端培训的机构哪个好,这五类人最适合转Web前端
  8. Python 魔方方法
  9. HEVC参考软件HM的使用
  10. Python Matplotlib绘制多子图准备训练数据和GIF动画实践