[返回科技频道首页]·[所有跟帖]·[ 回复本帖 ] ·[热门原创] ·[繁體閱讀]·[版主管理]
凉了!张三同学没答好「进程间通信」,被面试官挂了....
送交者: 婉君[☆★★★不服⭐来赌★★★☆] 于 2020-08-02 2:56 已读 1173 次  

婉君的个人频道

原标题:凉了!张三同学没答好「进程间通信」,被面试官挂了....


6park.com

来源 | 小林coding 6park.com

头图 | 图虫 6park.com

前言 6park.com

开场小故事 6park.com

6park.com

炎炎夏日,张三骑着单车去面试花了 1 小时,一路上汗流浃背。 6park.com

结果面试过程只花了 5 分钟就结束了,面完的时候,天还是依然是亮的,还得在烈日下奔波 1 小时回去。 6park.com

面试五分钟,骑车两小时。 6park.com

你看,张三因面试没准备好,吹空调的时间只有 5 分钟,来回路上花了 2 小时晒太阳,你说惨不惨? 6park.com

所以啊,炎炎夏日,为了能延长吹空调的时间,我们应该在面试前准备得更充分些,吹空调时间是要自己争取的。 6park.com

很明显,在这一场面试中, 张三在进程间通信这一块没复习好,虽然列出了进程间通信的方式,但这只是表面功夫,应该需要进一步了解每种通信方式的优缺点及应用场景。 6park.com

说真的,我们这次一起帮张三一起复习下,加深他对进程间通信的理解,好让他下次吹空调的时间能长一点。 6park.com

6park.com

正文 6park.com

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。 6park.com

6park.com

Linux 内核提供了不少进程间通信的机制,我们来一起瞧瞧有哪些? 6park.com

管道 6park.com

如果你学过 Linux 命令,那你肯定很熟悉「|」这个竖线。 6park.com

上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令(ps auxf) 6park.com

的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。 6park.com

同时,我们得知上面这种管道是没有名字,所以「|」表示的管道称为匿名管道,用完了就销毁。 6park.com

管道还有另外一个类型是命名管道,也被叫做 FIFO,因为数据是先进先出的传输方式。 6park.com

在使用命名管道前,先需要通过 mkfifo 命令来创建,并且指定管道名字: 6park.com

myPipe 就是这个管道的名称,基于 Linux 一切皆文件的理念,所以管道也是 6park.com

以文件的方式存在,我们可以用 ls 看一下,这个文件的类型是 p,也就是 pipe(管道) 的意思: 6park.com

接下来,我们往 myPipe 这个管道写入数据: 6park.com

你操作了后,你会发现命令执行后就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。 6park.com

于是,我们执行另外一个命令来读取这个管道里的数据: 6park.com

可以看到,管道里的内容被读取出来了,并打印在了终端上,另外一方面,echo 那个命令也正常退出了。 6park.com

我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处,自然就是简单,同时也我们很容易得知管道里的数据已经被另一个进程读取了。 6park.com

那管道如何创建呢,背后原理是什么? 6park.com

那管道如何创建呢,背后原理是什么? 6park.com

匿名管道的创建,需要通过下面这个系统调用: 6park.com

这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 6park.com

fd[0],另一个是管道的写入端描述符 fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。 6park.com

6park.com

其实,所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。 6park.com

看到这,你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢? 6park.com

我们可以使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「 fd[0] 与 fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。 6park.com

6park.com

管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。那么,为了避免这种情况,通常的做法是: 6park.com

父进程关闭读取的 fd[0],只保留写入的 fd[1]; 6park.com

子进程关闭写入的 fd[1],只保留读取的 fd[0]; 6park.com

父进程关闭读取的 fd[0],只保留写入的 fd[1]; 6park.com

子进程关闭写入的 fd[1],只保留读取的 fd[0]; 6park.com

6park.com

所以说如果需要双向通信,则应该创建两个管道。 6park.com

到这里,我们仅仅解析了使用管道进行父进程与子进程之间的通信,但是在我们 shell 里面并不是这样的。 6park.com

在 shell 里面执行 A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。 6park.com

6park.com

所以说,在 shell 里通过「|」匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写 shell 脚本时,能使用一个管道搞定的事情,就不要多用一个管道,这样可以减少创建子进程的系统开销。 6park.com

我们可以得知,对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。 6park.com

另外,对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。 6park.com

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。 6park.com

消息队列 6park.com

前面说到管道的通信方式是效率低的,因此管道不适合进程间频繁地交换数据。 6park.com

对于这个问题,消息队列的通信模式就可以解决。比如,A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。同理,B 进程要给 A 进程发送消息也是如此。 6park.com

再来,消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。 6park.com

消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。 6park.com

消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。 6park.com

但邮件的通信方式存在不足的地方有两点,一是通信不及时,二是附件也有大小限制,这同样也是消息队列通信不足的点。 6park.com

消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX 和 MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。 6park.com

消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。 6park.com

共享内存 6park.com

消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。 6park.com

现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。 6park.com

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。 6park.com

6park.com

信号量 6park.com

用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。 6park.com

为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。 6park.com

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。 6park.com

信号量表示资源的数量,控制信号量的方式有两种原子操作: 6park.com

一个是 P 操作,这个操作会把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。 6park.com

另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程; 6park.com

一个是 P 操作,这个操作会把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。 6park.com

另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程; 6park.com

P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。 6park.com

接下来,举个例子,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为 1。 6park.com

6park.com

具体的过程如下: 6park.com

进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。 6park.com

若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。 6park.com

直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。 6park.com

进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。 6park.com

若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。 6park.com

直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。 6park.com

可以发现,信号初始化为 1,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。 6park.com

另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。 6park.com

例如,进程 A 是负责生产数据,而进程 B 是负责读取数据,这两个进程是相互合作、相互依赖的,进程 A 必须先生产了数据,进程 B 才能读取到数据,所以执行是有前后顺序的。 6park.com

那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为 0。 6park.com

6park.com

具体过程: 6park.com

如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B 就阻塞等待; 6park.com

接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B; 6park.com

最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。 6park.com

如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B 就阻塞等待; 6park.com

接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B; 6park.com

最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。 6park.com

可以发现,信号初始化为 0,就代表着是同步信号量,它可以保证进程 A 应在进程 B 之前执行。 6park.com

信号 6park.com

上面说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。 6park.com

信号跟信号量虽然名字相似度 66.66%,但两者用途完全不一样,就好像 Java 和 Java 的区别。 6park.com

在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号: 6park.com

运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如 6park.com

Ctrl+C 产生 SIGINT 信号,表示终止该进程; 6park.com

Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束; 6park.com

Ctrl+C 产生 SIGINT 信号,表示终止该进程; 6park.com

Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束; 6park.com

如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如: 6park.com

kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程; 6park.com

kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程; 6park.com

所以,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)。 6park.com

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。 6park.com

Socket 6park.com

前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。 6park.com

实际上,Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。 6park.com

我们来看看创建 socket 的系统调用: 6park.com

三个参数分别代表: 6park.com

domain 参数用来指定协议族,比如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机; 6park.com

type 参数用来指定通信特性,比如 SOCK_STREAM 表示的是字节流,对应 TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字; 6park.com

protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol 目前一般写成 0 即可; 6park.com

domain 参数用来指定协议族,比如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机; 6park.com

type 参数用来指定通信特性,比如 SOCK_STREAM 表示的是字节流,对应 TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字; 6park.com

protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol 目前一般写成 0 即可; 6park.com

根据创建 socket 类型的不同,通信的方式也就不同: 6park.com

实现 TCP 字节流通信:socket 类型是 AF_INET 和 SOCK_STREAM; 6park.com

实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM; 6park.com

实现本地进程间通信:「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket; 6park.com

实现 TCP 字节流通信:socket 类型是 AF_INET 和 SOCK_STREAM; 6park.com

实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM; 6park.com

实现本地进程间通信:「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket; 6park.com

接下来,简单说一下这三种通信的编程模式。 6park.com

针对 TCP 协议通信的 socket 编程模型 6park.com

针对 TCP 协议通信的 socket 编程模型 6park.com

6park.com

服务端和客户端初始化 socket,得到文件描述符; 6park.com

服务端调用 bind,将绑定在 IP 地址和端口; 6park.com

服务端调用 listen,进行监听; 6park.com

服务端调用 accept,等待客户端连接; 6park.com

客户端调用 connect,向服务器端的地址和端口发起连接请求; 6park.com

服务端 accept 返回用于传输的 socket 的文件描述符; 6park.com

客户端调用 write 写入数据;服务端调用 read 读取数据; 6park.com

客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。 6park.com

服务端和客户端初始化 socket,得到文件描述符; 6park.com

服务端调用 bind,将绑定在 IP 地址和端口; 6park.com

服务端调用 listen,进行监听; 6park.com

服务端调用 accept,等待客户端连接; 6park.com

客户端调用 connect,向服务器端的地址和端口发起连接请求; 6park.com

服务端 accept 返回用于传输的 socket 的文件描述符; 6park.com

客户端调用 write 写入数据;服务端调用 read 读取数据; 6park.com

客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。 6park.com

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。 6park.com

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。 6park.com

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。 6park.com

针对 UDP 协议通信的 socket 编程模型 6park.com

针对 UDP 协议通信的 socket 编程模型 6park.com

6park.com

UDP 是没有连接的,所以不需要三次握手,也就不需要像 TCP 调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因此也需要 bind。 6park.com

对于 UDP 来说,不需要要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个 socket 多台机器就可以任意通信,因此每一个 UDP 的 socket 都需要 bind。 6park.com

另外,每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口。 6park.com

针对本地进程间通信的 socket 编程模型 6park.com

针对本地进程间通信的 socket 编程模型 6park.com

本地 socket 被用于在同一台主机上进程间通信的场景: 6park.com

本地 socket 的编程接口和 IPv4 、IPv6 套接字编程接口是一致的,可以支持「字节流」和「数据报」两种协议; 6park.com

本地 socket 的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现; 6park.com

本地 socket 的编程接口和 IPv4 、IPv6 套接字编程接口是一致的,可以支持「字节流」和「数据报」两种协议; 6park.com

本地 socket 的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现; 6park.com

对于本地字节流 socket,其 socket 类型是 AF_LOCAL 和 SOCK_STREAM。 6park.com

对于本地数据报 socket,其 socket 类型是 AF_LOCAL 和 SOCK_DGRAM。 6park.com

本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。 6park.com

总结 6park.com

由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享一个内核空间。 6park.com

Linux 内核提供了不少进程间通信的方式,其中最简单的方式就是管道,管道分为「匿名管道」和「命名管道」。 6park.com

匿名管道顾名思义,它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。 6park.com

命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。 6park.com

消息队列克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。 6park.com

共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。 6park.com

那么,就需要信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作。 6park.com

与信号量名字很相似的叫信号,它俩名字虽然相似,但功能一点儿都不一样。信号是进程间通信机制中唯一的异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,这是为了方便我们能在任何时候结束或停止某个进程。 6park.com

前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。 6park.com

以上,就是进程间通信的主要机制了。你可能会问了,那线程通信间的方式呢? 6park.com

同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步: 6park.com

互斥的方式,可保证任意时刻只有一个线程访问共享资源; 6park.com

同步的方式,可保证线程 A 应在线程 B 之前执行; 6park.com

互斥的方式,可保证任意时刻只有一个线程访问共享资源; 6park.com

同步的方式,可保证线程 A 应在线程 B 之前执行; 6park.com

好了,今日帮张三同学复习就到这了,希望张三同学早日收到心意的 offer,给夏天划上充满汗水的句号。 6park.com

喜欢婉君朋友的这个贴子的话, 请点这里投票,“赞”助支持!
[举报反馈]·[ 婉君的个人频道 ]·[-->>参与评论回复]·[用户前期主贴]·[手机扫描浏览分享]·[返回科技频道首页]
帖子内容是网友自行贴上分享,如果您认为其中内容违规或者侵犯了您的权益,请与我们联系,我们核实后会第一时间删除。

所有跟帖:        ( 主贴楼主有权删除不文明回复,拉黑不受欢迎的用户 )


    用户名:密码:[--注册ID--]

    标 题:

    粗体 斜体 下划线 居中 插入图片插入图片 插入Flash插入Flash动画


         图片上传  Youtube代码器  预览辅助

    打开微信,扫一扫[Scan QR Code]
    进入内容页点击屏幕右上分享按钮

    楼主本栏目热帖推荐:

    >>>>查看更多楼主社区动态...






    [ 留园条例 ] [ 广告服务 ] [ 联系我们 ] [ 个人帐户 ] [ 版主申请 ] [ Contact us ]