Usage scenarios and precautions of Go channel





1 Signal notification

There are often such scenarios, when the information collection is complete, the downstream is notified to start calculating data:

import (
	"log"
	"time"
)

func main() {
    
    
	isOver := make(chan struct{
    
    })
	go func() {
    
    
		collectMsg(isOver)
	}()
	<-isOver
	calculateMsg()
}

// 采集
func collectMsg(isOver chan struct{
    
    }) {
    
    
	log.Println("开始采集")
	time.Sleep(3000 * time.Millisecond)
	log.Println("完成采集")
	isOver <- struct{
    
    }{
    
    }
}

// 计算
func calculateMsg() {
    
    
	log.Println("开始进行数据分析")
}

Program output:

2021/01/25 09:48:23 开始采集
2021/01/25 09:48:26 完成采集
2021/01/25 09:48:26 开始进行数据分析

If you only use notification operations, then the type uses struct{}. Because empty structures do not take up memory space in go, don't believe me.

func main() {
    
    
	res := struct{
    
    }{
    
    }
	fmt.Println("占用空间:", unsafe.Sizeof(res))
}

Program output:

占用空间: 0




2 Task execution timeout

When doing task processing, the processing time of the task cannot be guaranteed, and some timeout controls are usually added to handle exceptions.

func main() {
    
    
	select {
    
    
	case <-doWork():
		log.Println("任务在规定时间内结束")
	case <-time.After(1 * time.Second):
		log.Println("任务处理超时")
	}
}

func doWork() <-chan struct{
    
    } {
    
    
	ch := make(chan struct{
    
    })
	go func() {
    
    
		// 处理耗时任务
		log.Println("开始处理任务")
		time.Sleep(2 * time.Second)
		ch <- struct{
    
    }{
    
    }
	}()
	return ch
}

Program output:

2021/01/25 09:58:10 开始处理任务
2021/01/25 09:58:11 任务处理超时




3 Production and consumption model

The producer only needs to pay attention to the production, not to the consumer's consumption behavior, let alone whether the consumer has completed the execution. Consumers only care about consumption tasks, not how to produce.

func main() {
    
    
	ch := make(chan int, 10)
	go consumer(ch)
	go producer(ch)
	time.Sleep(3 * time.Second)
}

// 一个生产者
func producer(ch chan int) {
    
    
	for i := 0; i < 10; i++ {
    
    
		ch <- i
	}
	close(ch) // 关闭通道
}

// 消费者
func consumer(ch chan int) {
    
    
	for i := 0; i < 5; i++ {
    
    
		// 5个消费者
		go func(id int) {
    
    
			for {
    
    
				item, ok := <-ch
				// 如果等于false 说明通道已关闭
				if !ok {
    
    
					return
				}
				fmt.Printf("消费者:%d,消费了:%d\n", id, item)

				time.Sleep(50 * time.Millisecond) // 给别人一点机会不会吃亏
			}
		}(i)
	}
}

Program output:

消费者:4,消费了:0
消费者:0,消费了:1
消费者:1,消费了:2
消费者:2,消费了:3
消费者:3,消费了:4
消费者:3,消费了:6
消费者:1,消费了:8
消费者:2,消费了:7
消费者:0,消费了:5
消费者:4,消费了:9




4 Data transfer

For an interesting question for geeks, suppose there are 4 goroutines, numbered 1, 2, 3, 4. Every 3 seconds, a goroutine prints out its own number. Now let you write a program that requires the output numbers to always be printed in the order of 1, 2, 3, 4. Similar to the picture below:
Insert picture description here

type token struct{
    
    }

func main() {
    
    
	num := 4
	var chs []chan token
	// 4 个 channel
	for i := 0; i < num; i++ {
    
    
		chs = append(chs, make(chan token))
	}
	// 4 个 协程
	for j := 0; j < num; j++ {
    
    
		go worker(j, chs[j], chs[(j+1)%num])
	}
	// 先把令牌交给第一个
	chs[0] <- struct{
    
    }{
    
    }
	select {
    
    }
}

func worker(id int, currentCh chan token, nextCh chan token) {
    
    
	for {
    
    
		// 对应 work 取得令牌
		token := <-currentCh
		fmt.Println(id + 1)
		time.Sleep(3 * time.Second)
		// 传递给下一个
		nextCh <- token
	}
}

Program output:

1
2
3
4
1
2
3
4
.
.
.




5 Control the number of concurrent

I often write some scripts to pull data internally or externally in the wee hours of the morning, but if concurrent requests are not controlled, it will often cause groutine to overflow, and then fill up the CPU resources. Often things that cannot be controlled mean that bad things will happen. For us, the number of concurrency can be controlled through channels.

func main() {
    
    
	limit := make(chan struct{
    
    }, 10)
	jobCount := 100
	for i := 0; i < jobCount; i++ {
    
    
		go func(index int) {
    
    
			limit <- struct{
    
    }{
    
    }
			job(index)
			<-limit
		}(i)
	}

	time.Sleep(30 * time.Second)
}

func job(index int) {
    
    
	// 耗时任务
	time.Sleep(2 * time.Second)
	fmt.Printf("任务:%d 已完成\n", index)
}

The above code controls that only 10 and coroutines are executed at any time

Of course, sync.waitGroup can also control the number of concurrent coroutines:

func main() {
    
    
	var wg sync.WaitGroup
	jobCount := 100
	limit := 10
	for i := 0; i <= jobCount; i += limit {
    
    
		index := 0
		if i-limit >= 0 {
    
    
			index = i - limit
		}
		for j := index; j < i; j++ {
    
    
			wg.Add(1)
			go func(item int) {
    
    
				defer wg.Done()
				job(item)
			}(j)
		}
		wg.Wait()
		fmt.Println("------------")
	}
}

func job(index int) {
    
    
	// 耗时任务
	time.Sleep(1 * time.Second)
	fmt.Printf("任务:%d 已完成\n", index)
}




6 custom mutex

A small mutex lock can be implemented through channel. By setting a channel with a buffer of 1, if data is successfully sent to the channel, it means that the lock is obtained, otherwise the lock is taken by someone else, waiting for someone to unlock it.

type ticket struct{
    
    }

type Mutex struct {
    
    
	ch chan ticket
}

// 创建一个缓冲区为1的通道作
func newMutex() *Mutex {
    
    
	return &Mutex{
    
    ch: make(chan ticket, 1)}
}

// 谁能往缓冲区为1的通道放入数据,谁就获取了锁
func (m *Mutex) Lock() {
    
    
	m.ch <- struct{
    
    }{
    
    }
}

// 解锁就把数据取出
func (m *Mutex) unLock() {
    
    
	select {
    
    
	case <-m.ch:
	default:
		panic("已经解锁了")
	}
}

func main() {
    
    
	mutex := newMutex()
	go func() {
    
    
		// 如果是1先拿到锁,那么2就要等1秒才能拿到锁
		mutex.Lock()
		fmt.Println("任务1拿到锁了")
		time.Sleep(1 * time.Second)
		mutex.unLock()
	}()
	go func() {
    
    
		mutex.Lock()
		// 如果是2拿先到锁,那么1就要等2秒才能拿到锁
		fmt.Println("任务2拿到锁了")
		time.Sleep(2 * time.Second)
		mutex.unLock()
	}()
	time.Sleep(500 * time.Millisecond)
	// 用了一点小手段这里最后才能拿到锁
	mutex.Lock()
	mutex.unLock()
	close(mutex.ch)
}




Pay attention to which operations of the channel will cause panic?


1 Closing a channel with a nil value will cause panic

package main

func main() {
    
    
  var ch chan struct{
    
    }
  close(ch)
}

Insert picture description here

2 Closing a closed channel will cause panic

package main

func main() {
    
    
	ch := make(chan struct{
    
    })
	close(ch)
	close(ch)
}

Insert picture description here

3 Send data to a closed channel

package main

func main() {
    
    
	ch := make(chan struct{
    
    })
	close(ch)
	ch <- struct{
    
    }{
    
    }
}

Insert picture description here

Guess you like

Origin blog.csdn.net/QiuHaoqian/article/details/113103478