本文主要是介绍12.1 通道-使用通道、带缓冲的通道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
使用通道
如果说Goroutine是一种支持并发的编程方式,那么通道就是一种在Goroutine之间建立通信的机制。(详细内容参见《Goroutine与MPG多线程文档》)
- 传统意义上讲,线程之间的数据是天然共享的,并不需要为通信建立专门的机制。
- 线程之间真正需要考虑的是如何避免针对共享数据的访问冲突,例如通过加锁,在一个线程读取共享数据时禁止其它线程修改该数据,或者相反。
- 然而通过锁来维护数据完整性的做法并非那么简单。如果不能确知程序中哪些部分需要读取数据,哪些部分又需要修改数据,以及它们的先后顺序,那么将很难预测可能发生的情况。即便是久经沙场的编程老手,也很难避免线程间因竞争共享资源而导致的BUG。
- 鉴于历史的教训,Go语言主张线程之间与其通过共享数据交换信息,不如建立通信机制。
除去在线程间交换数据以外,通道还有另外一个作用,即在宏观的异步过程中,借助某种形式的"停——等"逻辑,以满足微观的同步需求。
- 某个线程执行到某个特定位置,可能需要"停"下来,"等"另一个线程完成特定的操作后再继续执行,例如消费者线程必须等待生产者线程为其创造可供消费的资源才能继续消费。
- 传统意义上的线程同步,往往需要借助于互斥锁、信号量、条件变量等内核机制来实现。
- Go语言利用通道的阻塞I/O,在语言层面,以极简单的方式为异步线程提供了同步支持。
创建通道可使用内置函数make,收发消息可使用<-符号
- c := make(chan string) // 创建一个用于传输字符串数据的通道
- c <- "Task A of step 1 finished" // 向通道发送(写入)消息(数据)
- message := <-c // 从通道接收(读取)消息(数据)
// 并发中的异步问题
// 完全并发的"线程"以异步模式运行,其先后顺序完全不受控制
//
// step1和step2是一个任务的两步,需顺序执行,且step1是step2的前置条件;
// Task A 和Task B是step1的两个可并发执行的子任务。
// Task A of step 1
// +--------------------------+
// | |
// +--------+--------+--------+--------+
// 0 1 2 3 4
//
// Task B of step 1
// +-----------------+
// | |
// +--------+--------+--------+--------+
// 0 1 2 3 4
//
// Step 2
// +-----------------+
// | |
// +--------+--------+--------+--------+
// 0 1 2 3 4
package mainimport ("fmt""time"
)func step1a() {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)fmt.Println("Task A of step 1 finished")
}func step1b() {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)fmt.Println("Task B of step 1 finished")
}func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
}func main() {go step1a()step1b()step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Step 2 start...
// Task A of step 1 finished
// Step 2 finished
// 从上述输出可知,程序执行流程存在问题,step2在setep1完成之前就执行了。
// 并发中的同步协调
// 通过收发消息,以推送方式协调并发事件,使执行过程更加有序
//
// Task A of step 1
// +--------------------------+
// | |
// +--------+--------+--------+--------+--------+
// 0 1 2 3 4 5
//
// Task B of step 1
// +-----------------+
// | |
// +--------+--------+--------+--------+--------+
// 0 1 2 3 4 5
//
// Step 2
// +-----------------+
// | |
// +--------+--------+--------+--------+--------+
// 0 1 2 3 4 5 // 通道让数据能够进入或离开"线程",以在"线程"之间建立通信
//
// +---------------+ +----------------+
// | GOROUTINE |<-CHANNEL->| GOROUTINE |
// +---------------+ +----------------+
//
// 创建通道:通道 := make(chan 消息类型)
// 发送消息:通道 <- 消息
// 接收消息:消息 = <-通道
package main
import ("fmt""time"
)
// 通道可以作为函数的参数被传递
// 读写通道:通道 chan 消息类型
// 只读通道:通道 <-chan 消息类型
// 只写通道:通道 chan<- 消息类型func step1a(c chan string) {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)c <- "Task A of step 1 finished"//channel c的写入操作,会解阻塞c的可读
}
func step1b() {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)fmt.Println("Task B of step 1 finished")
}
func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
} func main() {c := make(chan string)go step1a(c) // 将通道c作为参数传给step1astep1b()message := <-c // 当channel c为空时,读取数据是阻塞的,需要等待。因此,// 借助channel解决了同步问题fmt.Println(message)step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Task A of step 1 finished
// Step 2 start...
// Step 2 finished
带缓冲的通道(缓冲通道的遍历-2种方法)
- 默认情况下的通道是不带缓冲区的,发送者向这样的通道发送消息,如果没有接收者前来接收,发送过程会发生阻塞。
- 如果不希望发送过程因所发消息无人接收而阻塞,可以使用带缓冲的通道
- 缓冲意味着可将消息缓存在通道内部,发送者不必等待,什么时候接收者准备就绪了,再从通道中将消息取走。
- 要创建带缓冲的通道,可在make函数的第二个参数中指定缓冲区的长度。
- c := make(chan string, 2) // 该通道最多缓存两条消息,向其发送更多消息将导致错误。
- 访问缓冲通道中的消息(2中方法):可以通过循环迭代缓冲通道中的所有消息。
- for i := 0; i < cap(c); i++ { message := <-c },cap(c)可以返回channel c的缓冲数据个数
- for message := range c { fmt.Println(message) }
// 有缓冲通道
// 无缓冲通道:如果没有接收者从通道中接收消息,发送者将会阻塞等待
// 有缓冲通道:如果没有接收者从通道中接收消息,发送者不会阻塞等待
// 无论哪种通道,如果没有发送者向通道发送消息,接收者都会阻塞等待
// 创建有缓冲通道:通道 := make(chan 消息类型, 消息数)
package main
import ("fmt""time"
)func step1a(c chan string) {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)c <- "Task A of step 1 finished" // task a写入完成信息
} func step1b(c chan string) {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)c <- "Task B of step 1 finished" // task b写入完成信息
}
func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
} func main() {c := make(chan string, 2)go step1a(c) // 执行task1ago step1b(c) // 执行task1bfor i := 0; i < cap(c); i++ {message := <-c // 遍历channle c,模拟确认所有前置已完成fmt.Println(message)}step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Task A of step 1 finished
// Step 2 start...
// Step 2 finished
这篇关于12.1 通道-使用通道、带缓冲的通道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!