如何理解channel

不知道golang初学者会不会有这样的疑惑,”为什么大家都说golang的channel 和 goroutine是并发编程的大杀器啊?“我一开始也是一头雾水,不理解channel能怎样用,但是看身边的同学把他玩出花,心里一个痒痒的。

通过channel,数据可以在goroutine(协程)中传递信息。我们在协程中可能会有数据交互,举个简单的例子,我们在主线程的生命周期中开一个处理协程,单协程处理dataCh。

之后可以开启其他协程在dataCh中发送数据。这样就初步完成数据交互。

此外,这个这也有一个思想,就是一种生产者——消费者模式。这样的数据传输可以避免不同协程共享内存的问题。数据的消费是有序的。

dataCh := make(chan int, 10)
go func(){
	for() {
		data := <- dataCh
		// handle data
	}
}()

channel的一些应用

上面的例子告诉我们,管道可以方便数据处理,让数据的处理不凌乱。通过管道,可以把数据放在一个协程上处理。

不过光是看上面的例子,本人还是没办法完全理解,为什么channel就是好呢,所以我也查询了些一些channel的例子。

定时器

定时器可以通过time包自带的方法使用。通过time包启动一个ticker,每过一个duration,就会运行一次

// 定时器
func tickerWorker() {
	ticker := time.Tick(time.Second)
	for {
		select {
		case <-ticker:
			fmt.Println("ticker")
		}
	}
}

生产者消费者

数据的生产和消费不在同一个协程,解耦两端

// 生产者消费者
func producerConsumer() {
	dataCh := make(chan int, 10)
	// sender
	for i := 0; i < 5; i++ {
		go func() {
			for {
				select {
				case dataCh <- rand.Int():

				}
			}
		}()
	}

	// receiver

	for d := range dataCh {
		fmt.Println(d)
	}

}

限流器

我觉得限流器很好玩,定义一个带缓存的管道,每次去管道加个锁,结束后释放

// 限流器,控制速率,当调用下游服务时,可以用channel控制速率
func limit() {
	limitCh := make(chan struct{}, 3)
	for i := 0; i < 20; i++ {
		limitCh <- struct{}{}
		go func() {
			defer func() { <-limitCh }()
			// 这里运行下游逻辑
			fmt.Println("这里运行下游逻辑")
		}()
	}
}

// 经典的channel题目
/*
	有 4 个 goroutine,编号为 1、2、3、4。
	每秒钟会有一个 goroutine 打印出自己的编号,要求写一个程序,让输出的编号总是按照 1、2、3、4、1、2、3、4… 的顺序打印出来
*/
func run() {
	chans := make([]chan int, 4)
	for i, _ := range chans {
		chans[i] = make(chan int)
	}
	for i, _ := range chans {
		go func(i int) {
			ch := chans[i]
			for {
				select {
				case data := <-ch:
					fmt.Println(data + 1)
					time.Sleep(time.Second)
					chans[(i+1)%4] <- (data + 1) % 4
				}
			}
		}(i)
	}
	chans[0] <- 0
}

提示:为什么有了管道,还需要sync.WaitGroup控制goroutine呢,因为如果光靠管道的通信结束协程的话,可能协程还没退出,main就先退出了,为了保障协程先退出

var wg sync.WaitGroup

func work() {
	defer wg.Done()
	xxxxx
}

func main() {
	
	for xxxx {
		wg.Add(1)
		work()
	}
	wg.Wait()
}

golang高级编程——常见并发模式

使用context控制并发的超时

func worker(ctx context.Context, wg *sync.WaitGroup) error {
    defer wg.Done()

    for {
        select {
        default:
            fmt.Println("hello")
        case <-ctx.Done():
            return ctx.Err()
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(ctx, &wg)
    }

    time.Sleep(time.Second)
    cancel()

    wg.Wait()
}