Escenarios de uso y precauciones del canal Go





1 notificación de señal

A menudo existen tales escenarios, cuando se completa la recopilación de información, se notifica al downstream para que comience a calcular los datos:

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("开始进行数据分析")
}

Salida del programa:

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

Si solo usa operaciones de notificación, entonces el tipo usa struct {}. Debido a que las estructuras vacías no ocupan espacio en la memoria en marcha, no me crea.

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

Salida del programa:

占用空间: 0




2 Tiempo de espera de ejecución de la tarea

Al realizar el procesamiento de tareas, no se puede garantizar el tiempo de procesamiento de la tarea y, por lo general, se agregan algunos controles de tiempo de espera para manejar excepciones.

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
}

Salida del programa:

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




3 Modelo de producción y consumo

El productor solo debe prestar atención a la producción, no al comportamiento de consumo del consumidor, y mucho menos si el consumidor ha completado la ejecución. Los consumidores solo se preocupan por las tareas de consumo, no por cómo producir.

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)
	}
}

Salida del programa:

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




4 Transferencia de datos

Para una pregunta interesante para los geeks, suponga que hay 4 goroutines, numerados 1, 2, 3, 4. Cada 3 segundos, una goroutine imprime su propio número. Ahora le permite escribir un programa que requiera que los números de salida siempre se impriman en el orden de 1, 2, 3, 4. Similar a la imagen de abajo:
Inserte la descripción de la imagen aquí

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
	}
}

Salida del programa:

1
2
3
4
1
2
3
4
.
.
.




5 Controle el número de concurrentes

A menudo escribo algunos scripts para extraer datos interna o externamente en las primeras horas de la mañana, pero si no se controlan las solicitudes simultáneas, a menudo hará que la groutine se desborde y luego llene los recursos de la CPU. A menudo, las cosas que no se pueden controlar significan que sucederán cosas malas. Para nosotros, el número de concurrencia se puede controlar a través de canales.

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)
}

El código anterior controla que solo se ejecuten 10 y corrutinas en cualquier momento

Por supuesto, sync.waitGroup también puede controlar el número de corrutinas simultáneas:

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 mutex personalizado

Se puede implementar un pequeño bloqueo de mutex a través del canal. Al configurar un canal con un búfer de 1, si los datos se envían correctamente al canal, significa que se obtiene el bloqueo; de lo contrario, otra persona toma el bloqueo, esperando que alguien lo desbloquee.

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)
}




¿Prestar atención a qué operaciones del canal causarán pánico?


1 Cerrar un canal con un valor nulo provocará pánico

package main

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

Inserte la descripción de la imagen aquí

2 Cerrar un canal cerrado provocará pánico

package main

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

Inserte la descripción de la imagen aquí

3 Enviar datos a un canal cerrado

package main

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

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/QiuHaoqian/article/details/113103478
Recomendado
Clasificación