Go语言并发之扇入和扇出

news2024/9/21 3:16:38

1、Go语言并发之扇入和扇出

编程中经常遇到扇入和扇出两个概念,所谓的扇入是指将多路通道聚合到一条通道中处理,Go 语言最简单的扇入

就是使用 select 聚合多条通道服务;所谓的扇出是指将一条通道发散到多条通道中处理,在Go语言里面具体实

现就是使用go关键字启动多个 goroutine 并发处理。

中国有句经典的哲学名句叫分久必合,合久必分,软件的设计和开发也遵循同样的哲学思想,扇入就是合,扇出

就是分。当生产者的速度很慢时,需要使用扇入技术聚合多个生产者满足消费者,比如很耗时的加密/解密服务;

当消费者的速度很慢时,需要使用扇出技术,比如Web服务器并发请求处理。扇入和扇出是Go并发编程中常用的

技术。

在这里插入图片描述

1.1 单链工作模式

如果我们没有使用扇入扇出的流程,就是传统的单链工作模式。

package main

import "fmt"

func A(n int) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for i := 1; i <= n; i++ {
			out <- fmt.Sprint("节点A-", i)
		}
	}()
	return out
}

func B(in <-chan string) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for c := range in {
			out <- "节点B" + c
		}
	}()
	return out
}

func C(in <-chan string) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for c := range in {
			out <- "节点C" + c
		}
	}()
	return out
}

func main() {
	componentA := A(9)
	componentB := B(componentA)
	componentC := C(componentB)
	for goods := range componentC {
		fmt.Println(goods)
	}
}
# 程序输出
节点C节点B节点A-1
节点C节点B节点A-2
节点C节点B节点A-3
节点C节点B节点A-4
节点C节点B节点A-5
节点C节点B节点A-6
节点C节点B节点A-7
节点C节点B节点A-8
节点C节点B节点A-9

1.2 扇入扇出模式

如果使用扇入扇出模式的话,我们在增加一个汇聚的功能函数就可以了。

package main

import (
	"fmt"
	"sync"
)

func A(n int) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for i := 1; i <= n; i++ {
			out <- fmt.Sprint("节点A-", i)
		}
	}()
	return out
}

func B(in <-chan string) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for c := range in {
			out <- "节点B" + c
		}
	}()
	return out
}

func C(in <-chan string) <-chan string {
	out := make(chan string)
	go func() {
		defer close(out)
		for c := range in {
			out <- "节点C" + c
		}
	}()
	return out
}

// 扇入的主要操作
func merge(ins ...<-chan string) <-chan string {
	var wg sync.WaitGroup
	out := make(chan string)
	// 将一个channel中的数据发送到out当中
	dispose := func(in <-chan string) {
		defer wg.Done()
		for c := range in {
			out <- c
		}
	}
	// 添加等待组的数量
	wg.Add(len(ins))
	// 扇入阶段,启动多个goroutine处理在channel当中的数据
	for _, cs := range ins {
		go dispose(cs)
	}
	// 等待所有的输入的数据ins处理完,再关闭输出的out
	go func() {
		wg.Wait()
		close(out)
	}()
	return out
}

func main() {
	componentA := A(9)
	componentB1 := B(componentA)
	componentB2 := B(componentA)
	componentB3 := B(componentA)
	// 汇聚三个进行扇入操作
	mergeComponent := merge(componentB1, componentB2, componentB3)
	// 扇出操作
	componentC := C(mergeComponent)
	for goods := range componentC {
		fmt.Println(goods)
	}
}
# 程序输出
节点C节点B节点A-2
节点C节点B节点A-3
节点C节点B节点A-1
节点C节点B节点A-5
节点C节点B节点A-7
节点C节点B节点A-4
节点C节点B节点A-6
节点C节点B节点A-8
节点C节点B节点A-9

1.3 扇入扇出法寻找素数

扇入扇出法,体现并发性能的,相信每个学golang的都应该听过扇入扇出法寻找素数,我们先来看看不使用扇入

扇出法的。

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 重复调用函数的生成器
var repeatFn = func(done <-chan interface{}, fn func() interface{}) <-chan interface{} {
	valueStream := make(chan interface{})
	go func() {
		defer close(valueStream)
		for {
			select {
			case <-done:
				return
			case valueStream <- fn():
			}
		}
	}()
	return valueStream
}

// 转为int的channel
var toInt = func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int {
	intStream := make(chan int)
	go func() {
		defer close(intStream)
		for v := range valueStream {
			select {
			case <-done:
				return
			case intStream <- v.(int):
			}
		}
	}()
	return intStream
}

// 返回素数
var primeChanArr = func(done <-chan interface{}, intStream <-chan int) <-chan interface{} {
	primeStream := make(chan interface{})
	go func() {
		defer close(primeStream)
		// intStream一直会循环生成
		for integer := range intStream {
			integer -= 1
			prime := true
			for divisor := integer - 1; divisor > 1; divisor-- {
				if integer%divisor == 0 {
					prime = false
					break
				}
			}
			if prime {
				select {
				case <-done:
					return
				case primeStream <- integer:
				}
			}
		}
	}()
	return primeStream
}

// 将另外一个channel的数据从全部倒腾出来
var take = func(done <-chan interface{}, valueStream <-chan interface{}, num int) <-chan interface{} {
	takeStream := make(chan interface{})
	go func() {
		defer close(takeStream)
		for i := 0; i < num; i++ {
			select {
			case <-done:
				return
			case takeStream <- <-valueStream:
			}
		}
	}()
	return takeStream
}

func main() {
	// 返回一个随机数
	randNum := func() interface{} {
		return rand.Intn(50000000)
	}
	// 完成chan
	done := make(chan interface{})
	defer close(done)
	start := time.Now()
	// 生成整形数字
	randIntStream := toInt(done, repeatFn(done, randNum))
	// channel数组
	fmt.Printf("Primes:\n")
	// 取出10个素数
	for prime := range take(done, primeChanArr(done, randIntStream), 10) {
		fmt.Printf("%d\n", prime)
	}
	fmt.Printf("消耗时间:%v", time.Since(start))
}
# 程序输出
Primes:
24941317
36122539
6410693
10128161
25511527
2107939
14004383
7190363
45931967
2393161
消耗时间:22.3454984s

然后我们来使用扇入删除法。

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
	"time"
)

// 重复调用函数的生成器
var repeatFn = func(done <-chan interface{}, fn func() interface{}) <-chan interface{} {
	valueStream := make(chan interface{})
	go func() {
		defer close(valueStream)
		for {
			select {
			case <-done:
				return
			case valueStream <- fn():

			}
		}
	}()
	return valueStream
}

// 转为int的channel
var toInt = func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int {
	intStream := make(chan int)
	go func() {
		defer close(intStream)
		for v := range valueStream {
			select {
			case <-done:
				return
			case intStream <- v.(int):
			}
		}
	}()
	return intStream
}

var primeChanArr = func(done <-chan interface{}, intStream <-chan int) <-chan interface{} {
	primeStream := make(chan interface{})
	go func() {
		defer close(primeStream)
		for integer := range intStream {
			integer -= 1
			prime := true
			for divisor := integer - 1; divisor > 1; divisor-- {
				if integer%divisor == 0 {
					prime = false
					break
				}
			}
			if prime {
				select {
				case <-done:
					return
				case primeStream <- integer:
				}
			}
		}
	}()
	return primeStream
}

// 将另外一个channel的数据从全部倒腾出来
var take = func(done <-chan interface{}, valueStream <-chan interface{}, num int) <-chan interface{} {
	takeStream := make(chan interface{})
	go func() {
		defer close(takeStream)
		for i := 0; i < num; i++ {
			select {
			case <-done:
				return
			case takeStream <- <-valueStream:
			}
		}
	}()
	return takeStream
}

var fanIn = func(done <-chan interface{}, channels ...<-chan interface{}) <-chan interface{} {
	var wg sync.WaitGroup
	multiplexedStream := make(chan interface{})
	multiplex := func(c <-chan interface{}) {
		defer wg.Done()
		for i := range c {
			select {
			case <-done:
				return
			case multiplexedStream <- i:
			}
		}
	}
	wg.Add(len(channels))
	for _, c := range channels {
		go multiplex(c)
	}
	// 等待所有的读取的完成
	go func() {
		wg.Wait()
		close(multiplexedStream)
	}()
	return multiplexedStream
}

func main() {
	// 生成随机数
	randNum := func() interface{} {
		return rand.Intn(50000000)
	}
	// 完成chan
	done := make(chan interface{})
	defer close(done)
	start := time.Now()
	randIntStream := toInt(done, repeatFn(done, randNum))
	cpuNumber := runtime.NumCPU()
	fmt.Printf("find cpu is : %d\n", cpuNumber)
	// channel数组
	chanArray := make([]<-chan interface{}, cpuNumber)
	fmt.Printf("Primes:\n")
	for i := 0; i < cpuNumber; i++ {
		chanArray[i] = primeChanArr(done, randIntStream)
	}
	for prime := range take(done, fanIn(done, chanArray...), 10) {
		fmt.Println(prime)
	}
	fmt.Printf("消耗时间:%v", time.Since(start))
}
# 程序输出
find cpu is : 8
Primes:
6410693
24941317
10128161
36122539
25511527
2107939
14004383
7190363
2393161
45931967
消耗时间:4.3099368s

1.4 扇入扇出法模拟任务处理

package main

import (
	"context"
	"log"
	"sync"
	"time"
)

// Task包含任务编号及任务所需时长
type Task struct {
	Number int
	Cost   time.Duration
}

// task channel生成器
func taskChannelGerenator(ctx context.Context, taskList []Task) <-chan Task {
	taskCh := make(chan Task)
	go func() {
		defer close(taskCh)
		for _, task := range taskList {
			select {
			case <-ctx.Done():
				return
			case taskCh <- task:
			}
		}
	}()
	return taskCh
}

// doTask处理并返回已处理的任务编号作为通道的函数
func doTask(ctx context.Context, taskCh <-chan Task) <-chan int {
	doneTaskCh := make(chan int)
	go func() {
		defer close(doneTaskCh)
		for task := range taskCh {
			select {
			case <-ctx.Done():
				return
			default:
				log.Printf("do task number: %d\n", task.Number)
				// task 任务处理
				// 根据任务耗时休眠
				time.Sleep(task.Cost)
				// 已处理任务的编号放入通道
				doneTaskCh <- task.Number
			}
		}
	}()
	return doneTaskCh
}

// fan-in意味着将多个数据流复用或合并成一个流
// merge函数接收参数传递的多个通道taskChs,并返回单个通道<-chan int
func merge(ctx context.Context, taskChs []<-chan int) <-chan int {
	var wg sync.WaitGroup
	mergedTaskCh := make(chan int)
	mergeTask := func(taskCh <-chan int) {
		defer wg.Done()
		for t := range taskCh {
			select {
			case <-ctx.Done():
				return
			case mergedTaskCh <- t:
			}
		}
	}
	wg.Add(len(taskChs))
	for _, taskCh := range taskChs {
		go mergeTask(taskCh)
	}
	// 等待所有任务处理完毕
	go func() {
		wg.Wait()
		close(mergedTaskCh)
	}()
	return mergedTaskCh
}

func main() {
	start := time.Now()
	// 使用context来防止goroutine泄漏,即使在处理过程中被中断
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	// taskList定义每个任务及其成本
	taskList := []Task{
		Task{1, 1 * time.Second},
		Task{2, 7 * time.Second},
		Task{3, 2 * time.Second},
		Task{4, 3 * time.Second},
		Task{5, 5 * time.Second},
		Task{6, 3 * time.Second},
	}
	// taskChannelGerenator是一个函数,它接收一个taskList并将其转换为Task类型的通道
	// 执行结果(int slice channel)存储在worker中
	// 由于doTask的结果是一个通道,被分给了多个worker,这就对应了fan-out处理
	taskCh := taskChannelGerenator(ctx, taskList)
	numWorkers := 4
	workers := make([]<-chan int, numWorkers)
	for i := 0; i < numWorkers; i++ {
		// doTask处理并返回已处理的任务编号作为通道的函数
		workers[i] = doTask(ctx, taskCh)
	}
	count := 0
	// merge从中读取已处理的任务编号
	for d := range merge(ctx, workers) {
		count++
		log.Printf("done task number: %d\n", d)
	}
	log.Printf("Finished. Done %d tasks. Total time: %fs", count, time.Since(start).Seconds())
}
# 程序输出
2023/06/10 17:51:04 do task number: 1
2023/06/10 17:51:04 do task number: 4
2023/06/10 17:51:04 do task number: 3
2023/06/10 17:51:04 do task number: 2
2023/06/10 17:51:05 do task number: 5
2023/06/10 17:51:05 done task number: 1
2023/06/10 17:51:06 do task number: 6
2023/06/10 17:51:06 done task number: 3
2023/06/10 17:51:07 done task number: 4
2023/06/10 17:51:09 done task number: 6
2023/06/10 17:51:10 done task number: 5
2023/06/10 17:51:11 done task number: 2
2023/06/10 17:51:11 Finished. Done 6 tasks. Total time: 7.009781s

1.5 扇入扇出法模拟流水线工作

假如我们有个流水线共分三个步骤,分别是 job1job2job3

package main

import (
	"fmt"
	"time"
)

func job1(count int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for i := 0; i < count; i++ {
			time.Sleep(time.Second)
			fmt.Println("job1 finish:", 1)
			outCh <- 1
		}
	}()
	return outCh
}

func job2(inCh <-chan int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for val := range inCh {
			// 耗时2秒
			time.Sleep(time.Second * 2)
			val++
			fmt.Println("job2 finish:", val)
			outCh <- val
		}
	}()
	return outCh
}

func job3(inCh <-chan int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for val := range inCh {
			val++
			fmt.Println("job3 finish:", val)
			outCh <- val
		}
	}()
	return outCh
}

func main() {
	t := time.Now()
	firstResult := job1(10)
	secondResult := job2(firstResult)
	thirdResult := job3(secondResult)
	for v := range thirdResult {
		fmt.Println(v)
	}
	fmt.Println("all finish")
	fmt.Println("duration:", time.Since(t).String())
}
# 程序输出
job1 finish: 1
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
3
all finish
duration: 21.0077945s

共计计算21秒,主要是因为 job2 中的耗时太久导致,现在我们的主要任务就是解决掉这个问题了。

这里只用了一个 job2 来处理 job1 的结果,如果我们能多开启几个 goroutine job2 并行处理会不会提升性能呢?

现在我们改进下代码,解决 job2 耗时的问题,需要注意一下,这里对ch的关闭也要作一下调整,由于启用了多个

job2 的 goroutine,所以在 job2 内部进行关闭了。

package main

import (
	"fmt"
	"sync"
	"time"
)

func job1(count int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for i := 0; i < count; i++ {
			time.Sleep(time.Second)
			fmt.Println("job1 finish:", 1)
			outCh <- 1
		}
	}()
	return outCh
}

func job2(inCh <-chan int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for val := range inCh {
			// 耗时2秒
			time.Sleep(time.Second * 2)
			val++
			fmt.Println("job2 finish:", val)
			outCh <- val
		}
	}()
	return outCh
}

func job3(inCh <-chan int) <-chan int {
	outCh := make(chan int, 2)
	go func() {
		defer close(outCh)
		for val := range inCh {
			val++
			fmt.Println("job3 finish:", val)
			outCh <- val
		}
	}()
	return outCh
}

func merge(inCh ...<-chan int) <-chan int {
	outCh := make(chan int, 2)
	var wg sync.WaitGroup
	for _, ch := range inCh {
		wg.Add(1)
		go func(wg *sync.WaitGroup, in <-chan int) {
			defer wg.Done()
			for val := range in {
				outCh <- val
			}
		}(&wg, ch)
	}
	// 重要注意,wg.Wait() 一定要在goroutine里运行,否则会引起deadlock
	go func() {
		wg.Wait()
		close(outCh)
	}()
	return outCh
}

func main() {
	t := time.Now()
	firstResult := job1(10)
	// 拆分成三个job2,即3个goroutine (扇出)
	secondResult1 := job2(firstResult)
	secondResult2 := job2(firstResult)
	secondResult3 := job2(firstResult)
	// 合并结果(扇入)
	secondResult := merge(secondResult1, secondResult2, secondResult3)
	thirdResult := job3(secondResult)
	for v := range thirdResult {
		fmt.Println(v)
	}
	fmt.Println("all finish")
	fmt.Println("duration:", time.Since(t).String())
}
# 程序输出
job1 finish: 1
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
job1 finish: 1
3
job2 finish: 2
job1 finish: 1
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
3
job1 finish: 1
job2 finish: 2
job3 finish: 3
3
job2 finish: 2
job3 finish: 3
3
all finish
duration: 12.0213193s

可以看到,性能提升了90%,由原来的22s减少到12s。上面代码中为了演示效果,使用的缓冲 chan 很小,如果调

大的话,性能更明显。

FAN-OUT模式:多个 goroutine 从同一个通道读取数据,直到该通道关闭。OUT 是一种张开的模式,所以又被称

为扇出,可以用来分发任务。

FAN-IN模式:1个 goroutine 从多个通道读取数据,直到这些通道关闭。IN 是一种收敛的模式,所以又被称为扇

入,用来收集处理的结果。

是不是很像扇子的状态,先展开(扇出)再全并(扇入)。

总结:在类似流水线这类的逻辑中,我们可以使用 FAN-IN 和 FAN-OUT 模式来提升程序性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/659916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

揭开视频识别(动作识别)的神秘面纱(附代码和demo)!

PaddleDetection 在计算机视觉中&#xff0c;视频识别和检测是一个重要的方向。历年来CVPR和ICCV等顶会文章中这类论文是最多的。视频识别和检测也是最有落地场景前景的&#xff0c;像人脸识别、动作检测、异常检测、行人重识别、行人计数等都是很有落地前景的应用方向。本文介…

shardingsphere第三课各种功能点及核心源码

一、功能点 1.审计功能 分片审计功能是针对数据库分片场景下对执行的 SQL 语句进行审计操作。分片审计既可以进行拦截操作&#xff0c;拦截系统配置的非法 SQL 语句&#xff0c;也可以是对 SQL 语句进行统计操作。 目前ShardingSphere内置的分片审计算法只有一个&#xff0c;…

C语言指针初进阶知识汇总

目录 1 指针 1.1 指针是乜嘢 1.2 指针的声明 1.3 运算符 1.4 简单的小例子们 1.5 指针的运算 1.5.1 指针加减运算 1.5.2 间址运算 1.5.3 指针的相减 两个地址之间的偏移量 2 指针与数组 2.1 指针和一维数组 2.1.1 定义数组及指针变量 2.1.2 能动手就不要瞎扯 2.…

当 Rokid 遇上函数计算

作者&#xff1a;王彬&#xff08;阿里云解决方案架构师&#xff09;、姚兰天&#xff08;Rokid 技术专家&#xff09;、聂大鹏&#xff08;阿里云高级技术专家 &#xff09; 公司背景和业务 Rokid 创立于2014年&#xff0c;是一家专注于人机交互技术的产品平台公司。Rokid 通…

2023/6/18周报

目录 摘要 论文阅读 1、题目和现有问题 2、工作流程 3、图神经网络模块 4、注意力网络 5、实验结果和分析 深度学习 1、GNN和GRU的融合 2、相关公式推导 总结 摘要 本周在论文阅读上&#xff0c;对基于图神经网络和改进自注意网络的会话推荐的论文进行了学习&#…

DPdisPCA算法原理笔记

概要 本文简单理顺《Differentially Private Distributed Principal Component Analysis》论文中的算法原理&#xff0c;它主要提出了一种基于差分隐私的分布式PCA算法&#xff0c;研究了该算法在实验数据以及真实数据中的表现&#xff0c;在参数相同的情况下本算法取得了和没…

OpenGL之深度测试

文章目录 深度测试深度测试函数源代码 深度测试 深度缓冲就像颜色缓冲(Color Buffer)&#xff08;储存所有的片段颜色&#xff1a;视觉输出&#xff09;一样&#xff0c;在每个片段中储存了信息&#xff0c;并且&#xff08;通常&#xff09;和颜色缓冲有着一样的宽度和高度。深…

Python3 条件控制与循环语句 | 菜鸟教程(八)

目录 一、Python3 条件控制 &#xff08;一&#xff09;Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 &#xff08;二&#xff09;if 语句 1、Python中if语句的一般形式如下所示&#xff1a; 2、注意&#…

<Linux开发>驱动开发 -之-platform 驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -之-platform 驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过程详细记…

基于SpringBoot+Vue的民宿管理平台系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

某音短视频评论采集翻页(移动安全篇)

本章测试一个作品的评论及翻页&#xff1a; 以及前面的抓x包方式&#xff0c;在专栏里也有很多&#xff0c;xposed抓包过sslping&#xff0c;通用版本等&#xff1b; https://codeooo.blog.csdn.net/category_11500477.html 翻页通过页码来控制&#xff1a; # -*- coding:…

Docker部署(1)——将jar包打成docker镜像并启动容器

在代码编写完成即将部署的时候&#xff0c;如果采用docker容器的方法&#xff0c;需要将jar包打成docker镜像并通过镜像将容器启动起来。具体的步骤如下。 一、首先下载java镜像 先使用docker search java命令进行搜索。 然而在拉取镜像的时候要注意不能直接去选择pull java ,…

kubernetes(k8s)理论篇

注意&#xff1a;kubeadm与docker是有版本要求的。 如果版本不兼容&#xff0c;初始化 kubeadm是会出现以下问题。 学习k8s掌握知识 基础概念 什么是 Pod 控制器类型 K8S 网络通讯模式 Kubernetes 构建 K8S 集群 资源清单 资源 掌握资源清单的语法 编写 Pod 掌握 Pod 的…

C国演义 [第四章]

第四章 全排列题目理解步骤树形图递归函数递归结束条件单层逻辑 代码 全排列II题目理解步骤递归函数递归结束条件单层逻辑 代码 全排列 力扣链接 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输…

简要介绍 | 深度学习中的自注意力机制:原理与挑战

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对深度学习中的自注意力机制进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 注2&#xff1a;"简要介绍"系列的所有创作均使用了AIGC工具辅助 深度学习中的自注意力机制&#xff1a;原…

android jetpack databinding的基本使用(java)

目录 databing的基本使用二级页面的绑定自定义BindingAdapter自定义BinddingAdapter的可选旧值双向绑定使用ObservableField来进行双向绑定 在recycleview中使用databinding databing的基本使用 开启databing android {........dataBinding{enable true} }修改布局文件 为布…

Unity UGUI6——UGUI进阶

一、UI 事件监听接口 ​ 目前所有的控件都只提供了常用的事件监听列表 ​ 如果想做一些类似长按&#xff0c;双击&#xff0c;拖拽等功能是无法制作的&#xff0c;或者想让 Image 和 Text&#xff0c;RawImage 三大基础控件能够响应玩家输入也是无法制作的 ​ 而事件接口就是…

Elasticsearch 基本使用(三)条件查询

条件查询 单条件查询matchdebug 查看分词结果match_phrase 多条件查询bool 子元素区别 单条件查询 match match 匹配字段&#xff0c;会对条件分词&#xff0c;然后每个词以or的关系在文档倒排索引内进行查询 GET bank/_search {"query": {"match": {&q…

一起学SF框架系列6.1-模块core-Resource

Java虽然提供了java.net.URL类和各种URL前缀处理程序来负责处理对各种资源的访问&#xff0c;但对于低级别资源的访问来说还是不够充分。例如&#xff0c;没有标准化的实现可用于访问需要从类路径中获取或者相对于一个ServletContext的资源&#xff1b;也没有检查所指向的资源是…

火影手游 问答题小抄

文章目录 Part.I IntroductionPart.II 一些常识Chap.I 基础常识Chap.II 人物相关Chap.III 原作相关Chap.III 游戏相关 Part.III 奥义 & 技能Chap.I S 忍Chap.II A 忍Chap.III B 忍Chap.IV C 忍 Part.IV 针对活动Chap.I 组织樱花祭Chap.II 樱花问答 Pary.V 名言Reference Pa…