golang 的chan 信道与并发

1、select 语句

语法如下:

select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}
selectswitchcase
selectcasecasecase

2、chan 类型

chan
// 声明
// 1、普通声明
// 声明一个 int 型的通道
var ch chan int
ch = make(chan int)
// 2、声明后直接赋值
var ch = make(chan int)
// 2、声明一个带有缓冲的通道
// 声明一个带有一个缓冲空间的通道
var ch = make(chan int,1)

3、chan 和 select 的简单使用

3.1 chan

package main

import "fmt"

func main() {
	var ch = make(chan int)

	go func(ch chan int){
		ch <- 1
	}(ch)

	fmt.Println("通道 ch 传递的值为:",<- ch)
}
# 运行结果
go run main.go 
通道 ch 传递的值为: 1

3.2 chan 和 select 配合使用

package main

import "fmt"

func main() {
	var ch = make(chan int)

	go func(ch chan int){
		ch <- 1
	}(ch)

	select {
	case i := <- ch:
		fmt.Println("通道 ch 传递的值为:",i)
	}
}
# 运行结果
go run main.go
通道 ch 传递的值为: 1

4、简单的实现并发

package main

import (
	"fmt"
	"time"
)

func main() {
	var ch = make(chan string)
	// key = taskId,value = sleep time
	var task = []int{3,2,1}

	startTime := time.Now()
	for k, v := range task {
		go func(taskId,sleepTime int,ch chan string) {
			time.Sleep(time.Second*time.Duration(sleepTime))
			ch <- fmt.Sprintf("当前任务为:%d,休眠时间为:%d",taskId,sleepTime)
		}(k,v,ch)
	}
	for range task {
		fmt.Println(<- ch)
	}
	endTime := time.Now()
	fmt.Printf("程序执行完毕,任务总耗费时间为:%v",endTime.Sub(startTime))
}
# 程序执行结果
go run main.go                                                                                                                                      
当前任务为:2,休眠时间为:1
当前任务为:1,休眠时间为:2
当前任务为:0,休眠时间为:3
程序执行完毕,任务总耗费时间为:3.003920688s
# 由结果可以看出来,总计耗费时间为 3秒,结果显而易见产生了并发效果

5、通过select 控制协程超时

package main

import (
	"fmt"
	"time"
)

func main() {
	var ch = make(chan string)
	// key = taskId,value = sleep time
	var task = []int{3,2,1}
	// 超时时间设置
	var overtime = 2
	startTime := time.Now()
	for k, v := range task {
		go Run(k,v,ch,overtime)
	}
	for range task {
		fmt.Println(<- ch)
	}
	endTime := time.Now()
	fmt.Printf("程序执行完毕,任务总耗费时间为:%v",endTime.Sub(startTime))
}

// 超时控制
// 再开启一个协程,执行run 方法,超时后直接 done 掉
// 使用select 造成协程内阻塞
func Run(taskId,sleepTime int,ch chan string,overTime int) {
	var chRun = make(chan string)
	go run(taskId,sleepTime,chRun)

	select {
	case re := <-chRun:
		ch <- re
	case <- time.After(time.Second * time.Duration(overTime)):
		ch <- fmt.Sprintf("当前任务为:%d,休眠时间为:%d,该任务已超时",taskId,sleepTime)
	}
}

func run(taskId,sleepTime int,ch chan string) {
	time.Sleep(time.Second*time.Duration(sleepTime))
	ch <- fmt.Sprintf("当前任务为:%d,休眠时间为:%d",taskId,sleepTime)
}
# 超时时间配置为2秒,运行结果
go run main.go 
当前任务为:2,休眠时间为:1
当前任务为:1,休眠时间为:2,该任务已超时
当前任务为:0,休眠时间为:3,该任务已超时
程序执行完毕,任务总耗费时间为:2.003472001s

# 超时时间配置为3秒,运行结果
当前任务为:2,休眠时间为:1
当前任务为:1,休眠时间为:2
当前任务为:0,休眠时间为:3,该任务已超时
程序执行完毕,任务总耗费时间为:3.000574019s
# 通过结果可以看出,当程序运行超时后,select 阻塞,等到接收到 time.After()的通道数据,从而达到超时效果

6、以缓冲通道的方式控制协程数量

package main

import (
	"fmt"
	"time"
)

func main() {
	// key = taskId,value = sleep time
	var task = []int{3,2,1}
	// 注意此时也要给 通信的通道 对应的缓冲空间,不然程序会done掉
	var ch = make(chan string,len(task))

	// 超时时间设置
	var overtime = 3
	// 通过控制缓冲空间大小,控制协程数量
	var limitCh = make(chan bool,1)
	startTime := time.Now()
	for k, v := range task {
		limitCh <- true

		go LimitChan(k,v,ch,overtime,limitCh)
	}
	for range task {
		fmt.Println(<- ch)
	}
	endTime := time.Now()
	fmt.Printf("程序执行完毕,任务总耗费时间为:%v",endTime.Sub(startTime))
}
// 以缓冲通道的方式,限制协程数量,可以防止程序无限开启协程数量
func LimitChan(taskId,sleepTime int,ch chan string,overTime int,limitCh chan bool) {
	Run(taskId,sleepTime,ch ,overTime)
	// 任务执行完毕后,释放一个空间
	<- limitCh
}
// 超时控制
// 再开启一个协程,执行run 方法,超时后直接 done 掉
// 使用select 造成协程内阻塞
func Run(taskId,sleepTime int,ch chan string,overTime int) {
	var chRun = make(chan string)
	go run(taskId,sleepTime,chRun)

	select {
	case re := <-chRun:
		ch <- re
	case <- time.After(time.Second * time.Duration(overTime)):
		ch <- fmt.Sprintf("当前任务为:%d,休眠时间为:%d,该任务已超时",taskId,sleepTime)
	}
}

func run(taskId,sleepTime int,ch chan string) {
	time.Sleep(time.Second*time.Duration(sleepTime))
	ch <- fmt.Sprintf("当前任务为:%d,休眠时间为:%d",taskId,sleepTime)
}
# 此时程序的执行结果为:
当前任务为:0,休眠时间为:3,该任务已超时
当前任务为:1,休眠时间为:2
当前任务为:2,休眠时间为:1
程序执行完毕,任务总耗费时间为:6.007556643s
# 当创建两个空间的缓冲通道时,结果为:
当前任务为:1,休眠时间为:2
当前任务为:0,休眠时间为:3,该任务已超时
当前任务为:2,休眠时间为:1
程序执行完毕,任务总耗费时间为:3.007228336s

# 从结果分析很明显可以看到,第一次只开启了一个协程,得到的结果放到了 ch 的缓冲空间,如果没有给 ch 足够的缓冲空间,程序会 deadlock

7、需要注意的点

// 这个点当时没注意到,结果纠结了半天,然后又重新分析了一遍,才发现这个问题点
// time.After 和 time.Now().After() 
// After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed
time.After(d Duration) <-chan Time
// time 包的 After 方法 返回一个 通信结构的 time  
// 可以通过该方法来实习超时控制
// Example
select { 
case m := <-c:     handle(m) 
case <-time.After(10 * time.Second):     
	fmt.Println("timed out") 
}
https://www.jianshu.com/p/42e89de33065