在多进程系统中,进程间通信是必不可少的。在 Golang 中,通过 channel、共享内存、管道和信号等方式进行进程间通信。
一、channel
channel 是 Golang 中的一种数据类型,可用于协程之间的通信。channel 可以将数据从一个协程中传递到另一个协程中。
在使用 channel 时,需要定义 channel 的类型(如 chan int),并通过 make 函数创建一个 channel 对象。使用 channel 的时候,可以通过 <- 和 -> 进行读写操作。
例如:
```
ch := make(chan int)
go func() {
ch <- 1
}()
i := <- ch
```
通过 make 函数创建了一个类型为 int 的 channel 对象。在一个协程中向 channel 中写入了 1,在另一个协程中从 channel 中读取了数据。需要注意的是,当一个协程试图从一个没有数据的 channel 中读取数据时,该协程会阻塞。
channel 还有一个重要的特性就是可以进行关闭操作。当一个 channel 被关闭后,读取方会不断的读取已经存在的数据,直到 channel 中的数据被全部读取完为止。
```
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for i := range ch {
fmt.Println(i)
}
```
通过这段代码,我们可以看到,虽然在协程中写入了 5 次数据,但在读取过程中只输出了 0 到 4 个数字。
二、共享内存
共享内存是指多个进程可以通过共享一块内存区域进行数据交换。在 Golang 中,可以通过 sync 包中的 Mutex 和 RWmutex 类型来实现共享内存。
Mutex 是一种排他锁,用于在多个进程之间协调对共享资源的访问。例如:
```
var mu sync.Mutex
var count int
func main() {
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
defer mu.Unlock()
count++
}()
}
time.Sleep(time.Second)
fmt.Println(count)
}
```
在这段代码中,我们通过互斥锁来协调对 count 变量的访问,保证多个协程同时修改 count 变量的时候,能够正确的得到最终的结果。
而 RWmutex 则是用于读写分离的场景,主要用于在读多写少的情况下提高性能。例如:
```
var mu sync.RWMutex
var m = make(map[string]int)
func main() {
for i := 0; i < 10; i++ {
go func() {
mu.RLock()
defer mu.RUnlock()
fmt.Println(m)
}()
}
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
defer mu.Unlock()
m["count"]++
}()
}
time.Sleep(time.Second)
fmt.Println(m)
}
```
在这段代码中,我们通过读写互斥锁保证多个协程同时读取和写入一个 map 变量时能够正确的得到最终的结果。
三、管道
管道是一种单向的、阻塞式的流,可以在两个进程之间传递数据。在 Golang 中,通过 os/exec 包中的 Command 方法和 io 包中的 Pipe 方法可以创建管道。
例如:
```
cmd1 := exec.Command("ls", "-lh")
cmd2 := exec.Command("grep", "test")
stdout1, _ := cmd1.StdoutPipe()
stdout2, _ := cmd2.StdoutPipe()
cmd1.Start()
cmd2.Start()
go func() {
defer func() {
stdout1.Close()
stdout2.Close()
}()
for {
b := make([]byte, 1024)
n, err := stdout1.Read(b)
if err == io.EOF {
break
}
stdout2.Write(b[:n])
}
}()
cmd1.Wait()
cmd2.Wait()
```
在这段代码中,我们通过创建两个进程 cmd1 和 cmd2,并在这两个进程之间通过管道进行数据传递。
四、信号
信号是一种异步通信机制,用于对多个进程进行通信和同步。在 Golang 中,可以通过 os 包中的 Signal 方法来捕捉和处理信号。
例如:
```
func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
fmt.Println(sig)
os.Exit(0)
}()
fmt.Println("awaiting signal")
<-time.After(30 * time.Second)
}
```
在这段代码中,我们创建了一个 channel 用于接收信号,并通过 Notify 方法监听了 SIGINT 和 SIGTERM 两类信号。当程序接收到一个信号时,会通过协程执行指定的操作。
扫码咨询 领取资料