Go语言基础之并发「channel」

一、并发概念

并发是指程序同时执行多个任务的能力。在计算机领域,可以理解为一个处理器同时处理多个任务的能力。

多线程是实现并发的一种方式,它是指在一个进程中开启多个线程,每个线程执行不同的任务。这些线程可以并发执行,提高程序的效率。但同时多个线程还需要协调配合,在访问共享资源时要保证互斥,否则会出现数据竞争问题。

Go语言中goroutine是实现并发的一个重要机制,本文将重点介绍goroutine之间如何进行通信,以及其中的一种通信机制channel(管道)

二、goroutine及其创建

goroutine是一种轻量级的线程,由Go语言的运行时(runtime)管理。使用goroutine可以方便地实现并发操作。

使用关键字go来创建goroutine:

func main() {

go func() {

fmt.Println("Hello, goroutine!")

}()

fmt.Println("Hello, main!")

}

上述代码中,通过调用go语句,创建一个新的goroutine,用于执行匿名函数。同时main函数也会照常执行。

三、channel介绍

channel是goroutine之间通信的一种方式,是一种类型安全的、同步的、阻塞式的数据传输机制。channel可以用来传输数据,或者用来同步goroutine的执行。

使用关键字make来创建channel:

// 声明一个int类型的channel

var ch chan int

// 创建一个int类型的channel

ch = make(chan int)

上述代码中,通过make函数创建了一个int类型的channel。channel是可以设置缓冲区大小的,也就是说可以先把数据存储在缓冲区中。

创建带缓冲区的channel:

// 声明一个带缓冲区的string类型channel

var ch chan string

// 创建一个带缓冲区大小为10的string类型channel

ch = make(chan string, 10)

以上代码创建了一个带缓冲区大小为10的string类型channel。

四、channel的读写操作

4.1 向channel写入数据

使用<-符号来向channel写入数据:

ch := make(chan int)

go func() {

// 向channel写入数据

ch <- 10

}()

// 从channel中读取数据

data := <-ch

fmt.Println(data) // 10

上述代码中,首先创建一个int类型的channel,然后在一个goroutine中向channel中写入数据10,在主函数中通过<-符号读取数据。

4.2 从channel中读取数据

使用<-符号来从channel中读取数据:

ch := make(chan int)

go func() {

// 向channel写入数据

ch <- 10

}()

// 从channel中读取数据

data := <-ch

fmt.Println(data) // 10

上述代码中,在goroutine中向channel中写入数据10,然后在主函数中通过<-符号读取数据。

如果channel中没有数据,读取操作会被阻塞,直到有数据可读。

五、channel的阻塞与非阻塞

5.1 阻塞式读取

当从channel中读取数据时,如果channel中没有数据可读,读取操作会被阻塞,直到有数据可读。

ch := make(chan int, 1)

go func() {

ch <- 10

}()

data := <-ch

fmt.Println(data) // 10

以上代码中,首先创建一个带缓冲区大小为1的int类型channel,向channel中写入数据10,然后立刻读取数据,因为缓冲区里有数据,所以读取成功,输出结果为10。

如果去掉make(chan int, 1)中的1,变成make(chan int),则会出现deadlock(死锁)的问题,因为主函数一直等待读取数据,而goroutine中没有向channel中写入数据,因为channel没有缓冲区,所以写入操作会被阻塞,从而导致死锁。

5.2 非阻塞式读取

可以使用select语句配合default子句来实现非阻塞式读取。

ch := make(chan int)

go func() {

time.Sleep(1 * time.Second)

ch <- 10

}()

select {

case data := <-ch:

fmt.Println(data) // 10

default:

fmt.Println("no data received")

}

以上代码中,在goroutine中向channel中写入数据,但是需要等待1秒钟才会完成。使用select语句来读取channel中的数据,如果channel中有数据可读,则读取数据并输出,如果channel中没有数据可读,则执行default子句。

六、关闭channel

关闭channel表示不能再向channel中写入数据,但是仍然可以从channel中读取数据。通常情况下,关闭channel是为了告诉接收方已经不会再有新的数据了。

在读取channel时,可以判断channel是否关闭:

ch := make(chan int)

go func() {

for i := 0; i < 10; i++ {

ch <- i

}

close(ch)

}()

for {

data, ok := <-ch

if !ok {

fmt.Println("channel closed")

break

}

fmt.Println(data)

}

以上代码中,创建一个int类型的channel,向其中写入数据,然后关闭channel。在for循环中不断读取channel中的数据,如果channel被关闭,则ok的值为false,跳出循环。

七、select语句

select语句可以同时对多个channel进行等待操作,一旦有任意一个channel可读,就执行相应的操作。

ch1 := make(chan int)

ch2 := make(chan string)

go func() {

ch1 <- 10

}()

go func() {

ch2 <- "hello"

}()

select {

case data := <-ch1:

fmt.Println("channel 1:", data)

case data := <-ch2:

fmt.Println("channel 2:", data)

}

以上代码中,使用select语句同时等待ch1和ch2两个channel中的数据,当任意一个channel中有数据可读时,就执行相应的操作。

八、总结

本文介绍了goroutine、channel以及channel的读写、阻塞、非阻塞、关闭和select等机制,通过实例代码详细介绍channel的应用场景和实现方法。

使用channel是Go语言中实现并发的重要手段,掌握它可以方便地进行并发编程,提高程序的效率和质量。

后端开发标签