linux进程与进程间通信

前置知识

linux中的进程

进程状态

在liunx中,每个进程每个时刻都是有状态的,可能的状态共有6个。

  • 可运行状态(TASK_RUNNING(task_running), 简称R): 系统立刻要或者正在CPU上运行,不过运行的时机是不确定的,这由进程调度器决定。
  • 可中断的睡眠状态(TAST_INTERRUNPTIBLE, 简称S):当进程正在等待某个事件(比如网络连接/信号量)到来,此时进程进入对应事件的等待状态中。当事件发生时,对应的等待队列中的一个或者多个进程会被唤醒。
  • 不可中断的睡眠状态(TAST_UNINTERRUNPTIBLE, 简称D):与上面的唯一区别在于这种状态的进程不会对任何信号响应。 这样的进程一般是在等待某个特殊事件,比如同步IOC操作 【???这个怎么醒的???】
  • 暂停状态或者跟踪状态(TASK_STOPPED或者TASK_TRACED,简称T):
    • 向不处于D状态的进程发送SIGSTOP信号,该进程会进入暂停状态,直到另一个进程向它发送SIGCONT信号,这个进程会转为R状态。
    • 处于跟踪状态的进程也会暂停,但是向它发送SIGCONT信号,这个进程不会恢复。比如我们使用GDB(调试进程)调试程序时,对应的进程运行到断点处就会停下来,这个时候,该进程就处于跟踪状态,向这个进程发送SIGCONT信号,这个进程不会恢复,只有当调试进程进行响应的系统调用或者退出之后,被跟踪的进程才能恢复。
  • 僵尸状态(TASK_DEAD-EXIT_ZOMBIE,简称Z):处于该状态的进程即将结束,该进程占用的绝大多数资源也被回收,不够还有一些信息比如退出码没有被删除。之所以保留这些信息,是因为该进程的父进程可能需要它们。由于此时进程主题已经被删除而只留下一个空壳,所以叫做僵尸进程。
  • 退出状态(TASK_DEAD-EXIT_DEAD,简称X):处于退出状态的进程会被结束掉,它占用的系统资源也会被操作系统自动回收。可能进入X状态的原因如下
    • 显式的让该进程的父进程忽略掉SIGCHLD信号(当一个进程结束的时候,内核会给父进程发送SIGCHLD)
    • 父子进程已经分离:分离后的子进程将不再共享父进程的代码,而是加载一个权限的程序。

简单来说,进程的状态只会在运行和非运行状态之间转换
linux进程与进程间通信_第1张图片

系统调用

  • 普通进程工作在用户空间,内核进程工作在内核空间
  • 普通进程式没有办法访问内核空间的
  • 用户空间不可与硬件交互,内核可以与硬件交互
  • 内核提供了一些API,用户进程通过调用这些API(这个行为叫做系统调用)来访问内核空间,从而操控系统或者内核数据
  • 系统调用和普通函数调用的区别:系统调用是向内核康健发出一个明确请求,而普通函数只是定义了如何获取一个给定的服务。
    linux进程与进程间通信_第2张图片
  • 为了保证操作系统的稳定和安全,内核提供了两个状态,内核态和用户态。大部分事件CPU处于用户态,这时CPU只能访问用户空间。当CPU调用系统API时,内核会先转为内核态,然后让CPU执行对应的内核函数。当内核函数执行完成知乎,内核会切换回用户态,并将执行结果返回给用户进程。
    linux进程与进程间通信_第3张图片

进程切换和调度

一个CPU中同一时刻只能运行一个进程,但是内核可以通过快速切换CPU上的进程造成多个进程同时运行的假象。

但是切换线程是有代价的,这涉及到进程状态的保存与恢复。另外,内核还需要考虑下次切换时运行哪个进程,什么时候切换等等。

  • 什么时候切换: 内核认为当前在CPU上运行的线程已经运行的足够久了,就会把这个进程换掉,让另外一个线程来执行。

解决上面问题的系统角度进程调度系统。

进程切换和进程调用是多个程序并发执行的基础。

关于同步

假设有两个线程正在操作一个共享内存
linux进程与进程间通信_第4张图片
linux进程与进程间通信_第5张图片
在这里插入图片描述

linux进程与进程间通信_第6张图片
解决方法:

临界区

加锁形成临界区
linux进程与进程间通信_第7张图片
linux进程与进程间通信_第8张图片

linux进程与进程间通信_第9张图片

原子操作

linux进程与进程间通信_第10张图片
linux进程与进程间通信_第11张图片
原子操作和临界区的区别在于原子操作不可以被中断。

原子操作必须由一个个单一的汇编指令表示,并且需要得到芯片级别的支持。

因为原子操作不可以被打断,所以原子操作只用于细粒度的简单操作。

临界区一般用语言层面的互斥锁来实现

进程间通信

什么是进程间通信

不同进程中进行数据交换、信号通知等行为。 IPC是其中一个分类

通信目的

  • 数据传输:一个进程需要将它的数据发送给另外一个进程
  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到
  • 通知事件:一个进程需要向另一个或者一组线程发送消息,通知它们发生了某种事件
  • 资源共享:多个线程之间共享资源 。 【需要通信、同步、锁】
  • 进程控制:有些进程希望完全控制另一个进程的执行(比如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时直到它的状态改变。

通信方式

linux进程与进程间通信_第12张图片
linux进程与进程间通信_第13张图片

消息队列

消息队列是消息的链表,存放在内存中,由内核维护

特点:

  • 消息队列允许一个或多个进程向它写入或者读取消息,并且每条消息都有类型
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
  • 与无名管道、有名管道一样,从消息队列中读出消息,消息队列中数据会被删除。
  • 消息队列中的消息是有格式的。
  • 只有内核重启或人工删除时,该消息才会被删除,若不人工删除消息队列,消息队列会一直存在于内存中
  • 消息队列标识符,来标识消息队列。消息队列在整个系统中是唯一的、

信号量

信号量主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。

信号量有以下两种类型:

  • 二值信号量: 最简单的信号量形式,信号量的值只能取0或1,类似于互斥锁。 注:二值信号量能够实现互斥锁的功能,但两者的关注内容不同。信号量强调共享资源,只要共享资源可用,其他进程同样可以修改信号量的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
  • 计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)。

共享内存

原理

共享内存允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。

大多数的共享内存的实现,都把由不同进程之间共享的内存安排为同一段物体内存。

首先我们都知道我们执行的每一个程序,它看到的内存其实都是虚拟内存,虚拟内存需要进行页表的映射将进程地址映射到物理内存,具体处理大致如下面的图

linux进程与进程间通信_第14张图片

优缺点

当中共享内存的大致原理相信我们可以看明白了,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信。

  • 共享内存是IPC通信当中传输速度最快的通信方式没有之一,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,数据不需要在两进程间复制,没有什么操作比这简单了。

  • 用共享内存进行数据通信,它对数据也没啥限制,也不要求通信的进程有一定的父子关系。

  • 最后就是共享内存的生命周期随内核。
    即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象),在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留;简单地说,共享内存区域对象的生命周期跟系统内核的生命周期是一致的,而且共享内存区域对象的作用域范围就是在整个系统内核的生命周期之内。

但是,共享内存也并不完美

  • 共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

系统函数

  1. 创建共享内存shmget
/*
 * 作用:创建共享内存
 * 参数:key_t key    程序需要提供一个参数key,它为共享内存段提供一个外部名。(每个IPC对象都与一个键 即key相关联,然后此键再由内核变换为标识符)。还有一个特殊的键值IPC_PRIVATE(也就是0), 它用于创建一个只属于该创建进程的新共享内存,通常不会用到(就是说key基本上不会是IPC_PRIVATE或者0, 0值是private的,不能在进程间共享。)
 * 		size_t size  以字节为单位指定需要共享的内存容量。
 * 		int shmflg   包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志是一样。权限标志对共享内存非常有用,因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放共享内存并设置它的权限,就可以避免数据被其他用户修改。
 * 返回值: 创建成功,则返回一个非负整数,即共享内存标识;
           如果失败,则返回-1.
*/
int shmget(key_t key, size_t size, int shmflg)

IPC对象是活动在内核级别的一种进程间通信的工具。存在的IPC对象通过它的标识符来引用和访问,这个标识符是一个非负整数,它唯一的标识了一个IPC对象,这个IPC对象可以是消息队列或信号量或共享存储器中的任意一种类型。在Linux系统中标识符被声明成整数,所以可能存在的最大标识符为65535。这里标识符与文件描述符有所不同,使用open函数打开一个文件时,返回的文件描述符的值为当前进程最小可用的文件描述符数组的下标。IPC对象删除或创建时相应的标识符的值会不断增加到最大的值,归零循环分配使用。

  1. 将共享内存端挂载到自己地址空间shmat
    第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,必须将其连接到一个进程的地址空间
/*
 * 作用:将共享内存端挂载到自己地址空间shmat
 * 参数:   int shmid            是由shmget函数返回的共享内存标识。
 * 			const void *shmaddr  指定共享内存连接到当前进程中的地址位置,通常为0,表示让系统来选择共享内存的地址。
 * 			int shmflg          是一组标志位,通常为0。它还可取:
 * 					                   SHM_RND,用以决定是否将当前共享内存段连接到指定的shmaddr上。该参数和shm_addr联合使用,用来控制共享内存连接的地址,除非只计划在一种硬件上运行应用程序,否则不要这样指定。填0让操作系统自己选择是更好的方式。
 * 					                  SHM_RDONLY单独使用则是指让它使连接的内存段只读,否则以读写方式连接此内存段
 * 返回值:调用成功返回挂载的虚拟地址空间起始地址,失败返回NULL
*/
void *shmat(int shmid, const void *shmaddr, int shmflg) 
  1. 与共享内存段分离 shmdt
/*
 * 作用:   与共享内存段分离 shmdt 
 * 			注意:仅仅是共享内存分离但并未删除它,其标识符及其相关数据结构都在;直到某个进程的IPC_RMID命令的调用shmctl特地删除它为止,
 * 				 只是使得该共享内存对当前进程不再可用。
 * 参数:   const void *shmaddr     shmat返回的地址指针。
 * 返回值: 成功时,返回0,失败时,返回-1.
*/
int shmdt(const void *shmaddr)
  1. shmctl 共享内存控制函数
/*
 * 作用:   shmctl 共享内存控制函数
 * 参数:   shm_id : 是shmget返回的共享内存标识符。
 *			cmd: 它可以取3个值:
 *             IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值
 *              IPC_SET   如果进程有足够的权限就把共享内存的当前关联值设置为shmid_ds结构中给出的值
 *             IPC_RMID  删除共享内存段
 *          buf:是一个指针,包含共享内存模式和访问权限的结构。
*/
 int shmctl(int shmid, int cmd, struct shmid_ds *buf)

第一个例子(C++):

用共享内存来再两进程间交换数据,比如交换一个结构体

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "memory.h"


typedef struct Stu{
     
    int  age;
    char name[10];
}Stu;

int main(int argc,char *argv[]){
     
    Stu s;
    strcpy(s.name, "jack");

    //创建共享内存段
    int id = shmget(1234, 8, IPC_CREAT | 0644);
    if (id == -1){
     
        perror("shmget");
        exit(1);
    }

    //挂载到进程的地址空间
    Stu *p = (Stu *)shmat(id, nullptr, 0);

    int i = 0;
    while (1){
     
        s.age = i++;
        memcpy(p, &s, sizeof(Stu));  //写到共享段中
        sleep(2);
    }

    return 0;
}
#include <iostream>
#include <string>
#include "unistd.h"
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct Stu{
     
    int  age;
    char name[10];
}Stu;

int main() {
     
    int id = shmget(1234, 8, 0);
    if (id == -1){
     
        perror("shmget");
        exit(1);
    }

    //挂载到进程的地址空间
    Stu *p = (Stu *)shmat(id, nullptr, 0);
    while (1){
     
        std::cout << "age = " << p->age << ", name = " << p->name << "\n";
        sleep(2);
    }

    return 0;
}

linux进程与进程间通信_第15张图片

  • 如果进程中的数据改变了,另一个进程会马上知道。

第2个例子(go)

golang不提供使用共享内存来通信的API,都是通过cgo来调用C语言来实现的。比较麻烦,暂时不写了。可以参考这个Golang直接操作共享内存

因为go提倡不要通过共享内存来通信,而应该通过通信来共享内存

总结:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信

管道

匿名管道

本质上是调用系统API

#include 
/*
 * 作用:创建一个匿名管道
 * 参数:fds[0]代表读; fds[1] 代表写
 * 返回值:若成功返回0,失败返回-1
*/
int pipe(int fd[2]);    

第1个例子(go)

package main

import "fmt"
import "os/exec"
import "bufio"
import "bytes"

func main() {
     
	//create cmd
	cmd_go_env := exec.Command("go", "env")
	cmd_grep := exec.Command("grep", "GOROOT")

	stdout_env, env_error := cmd_go_env.StdoutPipe()
	if env_error != nil {
     
		fmt.Println("Error happened about standard output pipe ", env_error)
		return
	}

	//env_error := cmd_go_env.Start()
	if env_error := cmd_go_env.Start(); env_error != nil {
     
		fmt.Println("Error happened in execution ", env_error)
		return
	}
	/*
	   a1 := make([]byte, 1024)
	   n, err := stdout_env.Read(a1)
	   if err != nil {
	           fmt.Println("Error happened in reading from stdout", err)
	           return
	   }

	   fmt.Printf("Standard output of go env command: %s", a1[:n])
	*/
	//get the output of go env
	stdout_buf_grep := bufio.NewReader(stdout_env)

	//create input pipe for grep command
	stdin_grep, grep_error := cmd_grep.StdinPipe()
	if grep_error != nil {
     
		fmt.Println("Error happened about standard input pipe ", grep_error)
		return
	}

	//connect the two pipes together
	stdout_buf_grep.WriteTo(stdin_grep)

	//set buffer for reading
	var buf_result bytes.Buffer
	cmd_grep.Stdout = &buf_result

	//grep_error := cmd_grep.Start()
	if grep_error := cmd_grep.Start(); grep_error != nil {
     
		fmt.Println("Error happened in execution ", grep_error)
		return
	}

	err := stdin_grep.Close()
	if err != nil {
     
		fmt.Println("Error happened in closing pipe", err)
		return
	}

	//make sure all the infor in the buffer could be read
	if err := cmd_grep.Wait(); err != nil {
     
		fmt.Println("Error happened in Wait process")
		return
	}
	fmt.Println(buf_result.String())

}

第2个例子

package main

import (
	"fmt"
	"os/exec"
)

func main(){
     
	cmd := exec.Command("echo", "-n", "pipe communicate") // ===>等效于shell命令  echo -n pipe communicate


	stdout1, err := cmd.StdoutPipe() // 将结果输出到一个管道中
	if  err != nil{
     
		fmt.Println("can't obtain the stdout pipe for command cmd: %s", err)
		return
	}


	if err := cmd.Start(); err != nil{
      //启动命令
		fmt.Println("cmd can not be startup: %s", err)
	}


	out := make([]byte, 30)
	n, err := stdout1.Read(out)
	if err != nil {
     
		fmt.Println("can't read data from the pipe: %s", err)
		return
	}
	fmt.Println(out[:n])

}
  • 如果 管道已经被写满了,那么写进程就会被阻塞。

如何读取管道中的数据:

  • 方法一:
	out := make([]byte, 30)
	n, err := stdout1.Read(out)
	if err != nil {
     
		fmt.Println("can't read data from the pipe: %s", err)
		return
	}
	fmt.Println(out[:n])
  • 方法二:将读取到的数据存放到缓冲区outbuf中
	var outbuf bytes.Buffer
	for{
     
		out := make([]byte, 30)
		n, err := stdout1.Read(out)
		if err != nil {
     
			if err  == io.EOF{
     
				break
			}else{
     
				fmt.Println("can't read data from the pipe: %s", err)
				return
			}
		}
		
		outbuf.Write(out[:n])
	}
	fmt.Println(outbuf.String())

  • 方法三:使用缓冲读取器从管道中读取数据
	outbuf1 := bufio.NewReader(stdout1) // 默认情况下,该读取器会携带一个长度为4096的缓冲区
	outbuf0, _, err := outbuf1.ReadLine()
	if err != nil{
     
		fmt.Println("can't read data from the pipe: %s", err)
		return
	}
	fmt.Println(string(outbuf0))

API:

   (c *Cmd) StdoutPipe() (io.ReadCloser, error)
   io.ReadCloser是一个扩展了io.Reader接口的接口类型,定义了可关闭的数据读取行为
作用: 将数据读取到p中
参数: []byte用来存储数据
返回值:
	* n: 
		*当管道中数据小于p的长度,n为实际读取到的字节数
		*当管道中数据大于等于p的长度,n为p的长度
			* 可能没有读完
		* 当管道中没有数据可以读取,n返回0,err返回io.EOF(用于判断数据释放已经被读完) 
type Reader interface {
     
	Read(p []byte) (n int, err error)
}

总结:

读管道:

  1. 管道中有数据,read返回实际读到的字节数。
  2. 管道中无数据:
    ①管道写端被全部关闭,read返回0 (好像读到文件结尾)
    ② 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据抵达,此时会让出cpu资源)

写管道:

  1. 管道读端全部被关闭, 进程异常终止 (操作系统发出SIGPIPE信号)
  2. 管道读端没有全部关闭:
    ①管道已满,write阻塞。
    ②管道未满,write将数据写入,并返回实际写入的字节数。

命名管道

本质上调用系统API:

#include <sys/stat.h>
/* 功能: 创建一个FIFO文件,该文件是真实存在于文件系统中的。
 * 参数: path 为创建命名管道的全路径
 *         mod 为创建命名管道的模式,指的是其存取权限
 * 		  dev为设备值,改值取决于文件创建的种类,它只在创建设备文件是才会用到。 
 * 返回值:成功返回 0 ,失败返回 -1
*/
int  mknod(const  char*  path, mode_t mod,  dev_t dev);

第1个例子(go)

package main

import (
	"fmt"
	"os"
)

func main(){
     
	reader, writer, err := os.Pipe()
	if err != nil{
     
		fmt.Println("can create named pipe")
		return
	}

	go func() {
     
		n, err := writer.Write([]byte("write"))
		if err != nil{
     
			fmt.Println("can write named pipe , %s", err)
			return
		}
		fmt.Println("write ", n)
	}()

	output := make([]byte, 100)
	n, err := reader.Read(output)
	if err != nil{
     
		fmt.Println("can'r read data from named piped")
	}
	fmt.Println("read ", n)
}

API:

  • 在os包中
作用:创建命名管道
返回值:
    r *File  管道输出端
	w *File  管道输入端
	err error 可能发生的错误
注意:命名管道的读/写必须同时进行,否则会阻塞到读/func Pipe() (r *File, w *File, err error)

上面创建的管道是并发不安全的,在io包中也提供了一个类似的包,创建了一个基于内存的有原子性操作保证的管道【这个管道不是基于文件系统的,所以没有作为中介的缓冲区,所以通过它传递的数据只会被复制一次。】

 作用:创建命名管道
返回值:
    r *File  管道输出端
	w *File  管道输入端
注意:命名管道的读/写必须同时进行,否则会阻塞到读/func Pipe() (r *File, w *File)

信号【没有用过,不太懂】

signal是IPC中唯一一种异步的通信方法,它的本质是用软件来模拟硬件的中断机制

信号的种类

使用kill命令查询当前系统所支持的信号

]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
  • linux支持的信号一共有62种(没有32和33号信号)
    • 1-31是不可靠信号(标准信号)
      • 对同一个进程来说,每种标准信号只会被记录并处理一次
      • 如果发送给某一个进程的标准信号的种类有多个,它们的处理顺序是不确定的
    • 34-64是可靠信号(实时信号)
      • 多个同种类的实时信号都可以记录在案,可以按照信号的发送顺序被处理

信号的来源

  • 键盘输入(比如Ctrl+c)
  • 硬件故障
  • 系统函数调用
  • 软件种的非法运算

如何处理信号

进程响应信号的方式:

  • 忽略
  • 捕捉
  • 执行默认操作

linux对每一个标准信号都有默认的操作方式,一定是下面的方式之一。

  • 终止进程
  • 忽略信号
  • 终止进程并保存内存信息
  • 停止进程
  • 恢复进程(如果进程已经停止)

对于大部分标准信号,我们可以在程序中自定义应该怎么响应它。

  • SIGKILL和SIGSTOP不能自行处理,也不能忽略,对它们的响应只能是系统的默认操作

golang与信号

  • go使用通道监听信号的到来。
  • 与信号有关的API在os/signal中提供
type Signal interface {
     
	String() string
	Signal() // to distinguish from other Stringers
}

API:

/*
* 作用:当操作系统当当前进程发送指定信号是发出通知。 当用户接收到相应信号之后,就不会执行默认操作,而是由用户自定义行为
*/

func Notify(c chan<- os.Signal, sig ...os.Signal)
/*
* 作用:恢复系统的默认操作
*/
func Stop(c chan<- os.Signal)

第一个例子:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"sync"
	"syscall"
)

func main() {
     
	sigRecv := make(chan os.Signal, 1)

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
     
		fmt.Println("11111")
		// 运行程序之后,会阻塞到这里。如果你ctrl+c,会发现程序没有终止,而是打印 receiver:Internpt
		for s := range sigRecv {
     
			fmt.Println("receiver:",s)
		}
		wg.Done()
	}()


	fmt.Println("--------")
	sigs := []os.Signal{
     syscall.SIGINT, syscall.SIGQUIT}
	signal.Notify(sigRecv, sigs...)

//	signal.Stop(sigRecv)
//	close(sigRecv)  // 必须关闭,否则sigRecv的接收通道会一致阻塞

	fmt.Println("****")
	wg.Wait()

}

socket编程

golang之socket编程详解

进程间共享数据

你可能感兴趣的:(#,golang)