go学习之goroutine和channel

文章目录

    • 一、goroutine(协程)
      • 1.goroutine入门
      • 2.goroutine基本介绍
        • -1.进程和线程说明
        • -2.程序、进程和线程的关系示意图
        • -3.Go协程和Go主线程
      • 3.案例说明
      • 4.小结
      • 5.MPG模式基本介绍
      • 6.设置Golang运行的CPU数
      • 7.协程并发(并行)资源竞争的问题
      • 8.全局互斥锁解决资源竞争
    • 二、管道
      • 1.为什么要使用channel
      • 2.channel的介绍
      • 3.管道的定义/声明channel
      • 4.channel使用的注意事项
      • 5.读写channel案例演示
      • 6.channel的遍历和关闭
        • -1.channel的关闭
        • -2.channel的遍历
      • 7.管道阻塞的机制
        • -1.应用实例2 --阻塞
        • 2-应用实例3
      • 8.channel使用细节和注意事项
        • 1)channel可以声明为只读,或者只写性质
        • 2)channel只读和只写的最佳实践案例
        • 3)使用select可以解决从管道取数据的阻塞问题
        • 4)goroutine中使用recover。解决协程中出现panic,导致程序崩溃问题

一、goroutine(协程)

1.goroutine入门

需求:要求统计1-20000的数字中,哪些是素数

分析思路

1)传统的方法,就是使用一个循环,循环的判断各个数是不是素数

2)使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成。这是就会使用到goroutine去完成,这时就会使用goroutine

2.goroutine基本介绍

-1.进程和线程说明

1)进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位

2)线程是进程的一个执行实例吗,是程序执行的最小单位,他是比进程更小的能独立运行的基本单位

3)一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行

4)一个程序至少有一个进程,一个进程至少有一个线程

-2.程序、进程和线程的关系示意图

在这里插入图片描述

-3.并发和并行

1)多线程程序在单核上运行,就是并发

2)多线程程序在多核上运行,就是并行

并发:因为是在一个CPU上,比如有10个线程,每个线程执行10毫秒(进行轮换操作),从人的角度来看,好像这10个线程都在运行,但是从微观上看,在某一个时间点来看,其实只有一个线程在执行,这就是并发

并行:因为是在多个CPU上(比如有10个CPU),比如有10个线程,每个线程执行10毫秒(各自在不同的CPU上执行),从人的角度上看,这10个线程都在运行,但是从微观上看,在某一个时间点,也是同时有10个线程在执行,这就是并行

-3.Go协程和Go主线程

1)Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程

2)Go协程的特点

  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 协程是轻量级的线程

示意图

在这里插入图片描述

3.案例说明

请编写一个程序,完成如下功能

1)在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔1秒输出"hello world"

2)在主线程也每隔一秒输出"hello golang",输出10次后,退出程序

3)要求主线程和goroutine同时执行

4)画出主线程和协程执行流程图

代码实现

package main
import (
	"fmt"
	"strconv"
	"time"
)
/*
1)在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔1秒输出"hello world"

2)在主线程也每隔一秒输出"hello golang",输出10次后,退出程序

3)要求主线程和goroutine同时执行
*/
//编写一个函数,每隔1秒输出"hello world
func test () {
	for i := 1; i <= 10; i++ {
		fmt.Println("test()hello world"+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}
func main() {
	go test() //开启了一个协程
    for i := 1; i <= 10; i++ {
		fmt.Println("main()hello world"+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

执行结果如下,我们可以发现主线程和go协程是同时执行的

在这里插入图片描述

go主线程与go协程的执行示意图

在这里插入图片描述

4.小结

1)主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常消耗cpu资源,

2)协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对少

3)golang的协程机制是重要的特点,可以轻松开启上万个协程。其他编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就凸显出golang在并发上的优势了

5.MPG模式基本介绍

在这里插入图片描述

1)M:操作系统的主线程(是物理线程)

2)P:协程执行需要的是上下文

3)G:协程

在这里插入图片描述

在这里插入图片描述

6.设置Golang运行的CPU数

介绍:为了充分利用多cpu的优势,在Golang程序中,设置运行的cpu数目

package main
import (
	"fmt"
	"runtime"
)

func main() {
	//获取当前系统CPU的数量
	num := runtime.NumCPU()
	//我这里设置num -1的cpu运行go程序
	runtime.GOMAXPROCS(num)
	fmt.Println("num=",num)
}

1)go1.8后,默认让程序运行在多个核上,可以不用设置了

2)go1.8前,还是要设置一下,可以更高效的利用CPU了

7.协程并发(并行)资源竞争的问题

需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中,最后显示出来。要求使用goroutine完成

分析思路:

1)使用goroutine来完成,效率高,但是会出现并发/并行安全问题

2)这里就提出了不同1goroutine如何通信的问题

代码实现

1)使用goroutine来完成(看看使用goroutine并发完成会出现什么问题?

2)在运行某个程序时,如何知道是否存在资源竞争的问题,方法很简单。在编译该程序时增加一个参数 -race即可

在这里插入图片描述

会发现map有些有值有些没有值,各个协程出现了资源竞争的问题

3)示意图

在这里插入图片描述

他们之间会出现资源竞争的问题

8.全局互斥锁解决资源竞争

不同的goroutine之间如何通信

1)全局变量加锁同步

2)channel

使用全局变量加锁同步改进程序

因为没有针对全局变量m加锁,因此会出现资源竞争的问题,代码会出现报错提示concurrent map writes

解决方案,-1加入互斥锁

package main
import (
	"fmt"
	"time"
	"sync"
)
//需求:现在要计算1-200的各个数的阶乘,
// 并且把各个数的阶乘放入到map中,最后显示出来。要求使用goroutine完成

//思路
//1.编写一个函数,来计算各个数的阶乘,并放入到map中
//2.我们爱动的协程是多个,统计的结果放入到map中
//2.map应该做出一个全局的

var (
	myMap = make(map[int]int,10) 
	//声明一个全局的互斥锁
	//lock是一个全局的互斥锁
	//sync 是包:synchornized 同步
	//Mutex是互斥的意思
	lock sync.Mutex
)

//test函数就是计算n的阶乘,将这个结果放入到map中
func test(n int) {
	res := 1
	for i :=1; i <=n;i++ {
		res *= i
	}

	//这里我们将res放入到myMap中
	//加锁
	lock.Lock()
    myMap[n]= res//concurrent map writes
	//解锁
   lock.Unlock()
}

func main() {
	//我们这里开启多个协程完成这个任务[200个协程]
	for i :=1; i <=15; i++ {
		go test(i)
	}
	//休眠10秒
	time.Sleep(time.Second * 5)
	//输出结果,遍历结果
	lock.Lock()
	for i,v :=range myMap {
		fmt.Printf("map[%d]=%d\n",i,v)
	}
    lock.Unlock()
}

我们的数的阶乘很大,结果会越界,我们可以改成sum +=uint64(i)

加锁解释

在这里插入图片描述

二、管道

1.为什么要使用channel

前面使用全局变量加锁同步来解决goroutine的通讯,但不完美

1)主线程在等待所有goroutine全部完成的时间很难确定。我们这里设置10秒,仅仅只是估算

2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随着主线程的退出而销毁

3)通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作

4)上面的种种分析都在呼唤一个新的通讯机制-channel

2.channel的介绍

1)channel本质就是一个数据结构-队列

2)数据是先进先出[FIFIO frist in first out]

3)线程安全,多goroutine访问时,不需要加锁,就是说在channel本身就是线程安全的

4)channel是有类型的,一个string的channel只能存放string数据

在这里插入图片描述

channel是线程安全,多个协程作同一个管道时,不会发生资源竞争的问题

3.管道的定义/声明channel

var 变量名 chan 数据类型

举例

var intChan chan int (intChan用于存放int数据)
var mapChan chan map[int]string (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan2 chan *Person
...

说明

1)channel是引用类型

2)channel必须初始化才能写入数据,即make后才能使用

3)管道是有类型的 intChan只能写入整数int

管道的初始化,写入数据到管道,从管道读取数据以及基本的注意事项

package main
import (
	"fmt"
)
func main() {
	//演示一下管道的使用
	//1.创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int,3)

	//2.看看intChan是什么
	fmt.Printf("intchan的值是=%v intChan本身的地址=%p\n",intChan,&intChan)
    
	//3.像管道写入数据
	intChan<-10
	num := 211
	intChan<- num

	//注意点,当我们在给管道写入数据时,不能超过其容量
	intChan<- 50
	//intChan<- 98 //会报错
	//4.输出看看管道的长度和cap(容量)
	fmt.Printf("channel len =%v cap=%v\n",len(intChan),cap(intChan)) // 3,3
    
	//5.从管道中读取数据
	var num2 int
	num2 = <-intChan
	fmt.Printf("num2=%v\n",num2) //10
	fmt.Printf("channel len =%v cap=%v\n",len(intChan),cap(intChan))//2,3
	 
	//6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
    num3 := <-intChan
    num4 := <-intChan
    // num5 := <-intChan
    // fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)//报错


	}   

4.channel使用的注意事项

1)channel中只能存放指定的数据类型

2)channel的数据放满后,就不能在放入了

3)如果从channel取出数据后,可以继续放入

4)在没有使用协程的情况下,如果channel数据取完了,再取就会报deadlock

5.读写channel案例演示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {
	//定义一个存放任意数据类型的管道3个数据
	// var  allChan chan interface{}
    allChan := make(chan interface{},3)

	allChan<-10
	allChan<-"tom jack"
	cat :=Cat{"小花猫",4}
	allChan<- cat
 
//我们希望获得管道中的第三个元素,则先将前2个推出
<-allChan
<-allChan

newCat :=<-allChan //从管道中取出来的cat是什么
fmt.Printf("newCat=%T,newCat=%v\n",newCat,newCat)//newCat=main.Cat,newCat={小花猫 4}
//下面的写法是错误的,编译不通过,则使用类型断言就可以通过
// fmt.Printf("newCat.Name=%v",newCat.Name)
a :=newCat.(Cat)
fmt.Printf("newCat.Name=%v",a.Name)//newCat.Name=小花猫
}

6.channel的遍历和关闭

-1.channel的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从谈channel读取数据

package main
import (
	"fmt"
)
func main() {
	intChan :=make(chan int,3)
	intChan<- 100
	intChan<- 200
	close(intChan) //close
	//这时不能够再写入到数channel
	//intChan<- 300 //panic: send on closed channel
	fmt.Println("okok~")
	//当管道关闭后,读取数据是可以的
	n1 := <-intChan
	fmt.Println("n1=",n1)
//输出如下
   // okok~
     //n1= 100
}
-2.channel的遍历

channel支持for-range的方式进行遍历,请注意两个细节

1)在遍历时,如果channel没有关闭,则会出现deadlock的错误

2)在遍历时,如果cahnnel已经关闭,则会正常遍历数据,遍历完成后,就会退出遍历

代码演示

package main
import (
	"fmt"
)
func main() {
	intChan :=make(chan int,3)
	intChan<- 100
	intChan<- 200
	close(intChan) //close
	//这时不能够再写入到数channel
	//intChan<- 300 //panic: send on closed channel
	fmt.Println("okok~")
	//当管道关闭后,读取数据是可以的
	n1 := <-intChan
	fmt.Println("n1=",n1)

	//遍历管道
    intChan2 :=make(chan int,100)
	for i :=0; i < 100; i++ {
        intChan2 <- i *2 //放入100个数据进去管道之中
	}
	//遍历:这种遍历是错误的,因为遍历过程中管道的长度会变化
	// for i :=0; i < len(intChan2);++ {

	// }
	//在遍历时,如果channel没有关闭,则回出现deadlock的错误

	//在遍历时,如果cahnnel已经关闭,则会正常遍历数据,遍历完成后,就会退出遍历
	close(intChan2)
	for v := range intChan2 {
        fmt.Println("v=",v)
	}
}

7.应用案例

-1.应用案例1

请完成goroutine和channel协同工作案例,具体要求

1)开启一个writeData协程,向管道intChan中写入50个整数

2)开启一个readData协程,从管道inChan中读取writeData写入的数据

3)注意:writeData和readData操作的是同一个管道

4)主线程需要等到writeData协程都完成工作才能退出

思路分析

在这里插入图片描述

看代码演示:

package main
import (
	"fmt"
	_"time"
)
//writeDtata
func writeData(intChan chan int) {
	for i :=0;i<=50;i++ {
		//放入数据
		intChan<- i
		fmt.Println("writeData",i)
		// time.Sleep(time.Second )
	}
	close(intChan)//关闭管道,不影响读
}

readDtata
func readData(intChan chan int,exitChan chan bool) {
	for {
		v,ok := <-intChan
		if !ok {
			break
		}
		//time.Sleep(time.Second )
		fmt.Printf("readData 读到的数据=%v\n",v)
	}
	//readData 读取完数据后,即任务完成
	exitChan<- true //数据读取完之后就网退出管道加入一个1
	close(exitChan)
}
func main() {
	//创建两个管道
	intChan := make(chan int,50)
	exitChan :=make(chan bool,1 )

	go writeData(intChan)
	go readData(intChan,exitChan)
	//time.Sleep(time.Second * 10)
	for {
		_, ok :=<-exitChan
		if !ok {
			break
		}
	}
}

7.管道阻塞的机制

-1.应用实例2 --阻塞
func main() {
 intChan :=make(chan int, 10) //10->50的话数据一下就放入了
 exitChan :=make(chan bool,1)
 //go readData(intChan,exitChan)
 
 //就是为了等待readData的协程完成
 for ——=range exitChan{
 fmt.Println("ok...")
 }
}

问题:如果注销掉go readData(intChan, exitChan)程序会怎么样

答:如果只是向管道写入数据,而没有读取,就会出现阻塞而deadLock,原因是intChan容量是10,而writeData会写入50个数据,因此会阻塞在writeData的ch <-i

2-应用实例3

1)需求:要求统计1 200000的数字中,哪些是素数?这个问题在本章开篇就提出了,
现在我们有goroutine和channel的知识后,就可以完成了[测试数据:80000]

2)分析思路:

  • 传统的方法,就是使用一个循环,循环的判断各个数是不是素数。

  • 使用并发/并行的方式,将统计素数的任务分配给多个(4个)goroutine去完成,

    完成任务时间短。

1.画出分析思路

在这里插入图片描述

2.代码实现

package main
import (
	"fmt"
	"time"
)
//向intChan放入 1-8000个数
func putNum(intChan chan int){
	for i := 0 ;i<8000; i++{
		intChan<- i
	}
	//关闭intChan
	close(intChan)
}

//从intchan中取出数据,并判断是否为素数,如果是就放入到primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){

	//使用for循环
	
	var flag bool
	for {
		time.Sleep(time.Millisecond)
		num,ok := <-intChan
		if !ok { //intChan取不到的时候,就退出这个主for循环
			break
		}
		flag = true //假设是素数
		//判断num是不是素数
		for i :=2;i<num;i++{
			if num %i ==0 { //说明i不是素数
				flag = false
				break
			}
		}
		if flag {
			//将这个数就放入到primeChan中
			primeChan<- num
		}

	}
	fmt.Println("有一个协程因为取不到数据没退出!")
	//这里我们还不能关闭primeChan
	//向exitChan 写入true
	exitChan<- true
}
func main() {
	intChan :=make(chan int,1000)
	primeChan :=make(chan int,2000) //放入结果
	//标识退出的管道
	exitChan :=make(chan bool ,4) //4个
	
	//开启一个协程,向intChan放入 1-8000个数
	go putNum(intChan)
	//开启4个协程,从intchan中取出数据,并判断是否为素数,如果是就放入到primeChan
	for i :=0;i<4; i++{
		go primeNum(intChan,primeChan,exitChan)
	}
	//这里我们主线程,进行处理
	go func() {
		for i :=0;i<4; i++{
			<-exitChan
		}
		//当我们从exitChan祛除了4个结果,就可以放心关闭primeChan
		close(primeChan)
	}()

	//遍历primeChan
	for {
		res,ok := <-primeChan
		if !ok {
			break
		}
		//将结果输出
		fmt.Printf("素数为=%d\n",res)
	}
	fmt.Println("main主线程退出")


}

说明:使用goroutine完成后,可以在使用传统的方法来统计一下,看看完成这个
任务,各自耗费的时间是多少?[用map保存primeNum]

使用go协程后,执行的速度,比普通方法提高至少4倍

8.channel使用细节和注意事项

1)channel可以声明为只读,或者只写性质
package main
import (
	"fmt"
)
func main() {
	//管道可以生命为只读或只写

	//1.在默认的情况下,管道是双向的。
	// var chan1 chan int //可读可写


	//2.声明为只写
	var chan2 chan<- int
	chan2 = make(chan int,3)
	chan2<- 20
	// num := <-chan2 err在这个管道中不可以读
	fmt.Println("chan2=",chan2)

	//3.声明为只读
	var chan3 <-chan int
	num2 := <-chan3
	// chan3<- 30 err 会报错,因为该管道为只读
	fmt.Println("num2=",num2)
}
2)channel只读和只写的最佳实践案例
package main
import (
	"fmt"
)

//ch chan<- int,这样ch就只能写操作
func send (ch chan<- int,exitChan chan struct{}){
	for i :=0; i < 10; i++ {
		ch <- i
	}
	close(ch)
	var a struct{}
	exitChan <- a
}
//ch <- chan int,这样ch就只能读操作了
func recv(ch <-chan int,exitChan chan struct{}){
	for {
		v,ok := <-ch
		if !ok {
			break
		}
		fmt.Println(v)
	}
	var a struct{}
	exitChan <- a
}
func main() {
	var ch chan int
	ch = make(chan int , 10)
	exitChan :=make(chan struct{},2)
	go send(ch,exitChan)
	go recv(ch,exitChan)
	var total = 0
	for _= range exitChan {
		total ++
		if total == 2 {
			break
		}
	}
	fmt.Println("结束...")
}
3)使用select可以解决从管道取数据的阻塞问题
package  main
import (
	"fmt"
	"time"
)
func main() {

	//使用select可以解决从管道读取数据阻塞问题

	//1.先定义一个管道 10个数据 int
	intChan :=make(chan int, 10)
	for i := 0 ; i < 10 ;i ++{
		intChan<- i
	}
	//2.定义一个管道5个数据string
	stringChan :=make (chan string , 5)
	for i := 0; i < 5 ; i++ {
		stringChan <- "hello" +fmt.Sprintf("%d",i)
	}

	//传统方法遍历管道时,如果不关闭会阻塞而导致 deadlock

	//问题,在实际开发中,可能我们不好确定什么时候关闭该管道
	//可以使用select 方式解决
	label:
	for {
		select  {
		case v := <-intChan :  //注意:这里如果 intChan一直没有关闭,不会导致deadlocks,会自动到下一个case
			fmt.Printf("从intChan读取的数据%d\n",v)
			time.Sleep(time.Second)
		case v := <-stringChan :
			fmt.Printf("从stringChan读取的数据%s\n",v)	
			time.Sleep(time.Second)
		default :
			fmt.Println("都取不到,不玩了,你可以加入逻辑")	
			time.Sleep(time.Second)
			return
			break label
		}

	}

}
4)goroutine中使用recover。解决协程中出现panic,导致程序崩溃问题
package main
import (
	"fmt"
	"time"
)
//函数1
func sayHello() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("hello world")
	}
}
//函数2
func test(){
	//这里试用贴defer + recover
	defer func() {
		//捕获test爬出的panic
		if err := recover(); err !=nil {
			fmt.Println("test()发生错误",err)
		}
	}()
	//定义了一个map
	var myMap map[int]string
	myMap[0] = "golang" //erro
}
func main(){
	go sayHello()
	go test()
	for i := 0; i < 10; i++ {
		fmt.Println("main() ok=",i)
		time.Sleep(time.Second)
	}
}

输出结果如下

在这里插入图片描述
说明:如果我们起了一个协程,但…是这个协程出现了panic,如果我们没有捕获这个panic。就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic,进行处理,这些即使这个协程发生的问题,但是主线程任然不受影响,可以继续运行

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode | 100. 相同的树

LeetCode | 100. 相同的树 OJ链接 判断两个节点是否等于空&#xff0c;两个都等于空就直接返回true如果一个等于空&#xff0c;另一个不等于空&#xff0c;说明false然后再判断两个树的值是否相等最后递归p的左&#xff0c;q的左&#xff0c;p的右&#xff0c;q的右 bool isS…

SQL注入漏洞的检测及防御方法

SQL注入&#xff08;SQL Injection&#xff09;是一种广泛存在于Web应用程序中的严重安全漏洞&#xff0c;它允许攻击者在不得到授权的情况下访问、修改或删除数据库中的数据。这是一种常见的攻击方式&#xff0c;因此数据库开发者、Web开发者和安全专业人员需要了解它&#xf…

【python】保存excel

正确安装了pandas和openpyxl库。 可以通过在命令行中输入以下命令来检查&#xff1a; pip show pandas pip show openpyxl 可以使用pip安装 pip install pandas pip install openpyxl#更新 pip install --upgrade pandas pip install --upgrade openpyxl 保存excel …

HNU-编译原理-讨论课2

讨论课安排&#xff1a;2次4学时&#xff0c;分别完成四大主题讨论 分组&#xff1a;每个班分为8组&#xff0c;每组4~5人&#xff0c;自选组长1人 要求和说明&#xff1a; 以小组为单位上台报告&#xff1b;每次每组汇报2个小主题&#xff0c;每组按要求在2个小主题中各选1…

零信任安全:远程浏览器隔离(RBI)的重要性

引言 在当今数字化时代&#xff0c;网络安全已成为个人和企业关注的焦点。随着网络攻击和恶意软件的不断增加&#xff0c;远程浏览器隔离(RBI)SAAS系统变得至关重要。本文将深入探讨远程浏览器隔离系统的重要性&#xff0c;以及它如何帮助用户保护其网络免受恶意软件和网络攻击…

2022年4月19日 Go生态洞察:Go开发者调查2021结果分析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

FO-like Transformation in QROM Oracle Cloning

参考文献&#xff1a; [RS91] Rackoff C, Simon D R. Non-interactive zero-knowledge proof of knowledge and chosen ciphertext attack[C]//Annual international cryptology conference. Berlin, Heidelberg: Springer Berlin Heidelberg, 1991: 433-444.[BR93] Bellare M…

Seaborn可视化图形绘制_Python数据分析与可视化

Seaborn可视化图形绘制 频次直方图、KDE和密度图矩阵图分面频次直方图条形图折线图 Seaborn的主要思想是用高级命令为统计数据探索和统计模型拟合创建各种图形&#xff0c;下面将介绍一些Seaborn中的数据集和图形类型。 虽然所有这些图形都可以用Matplotlib命令实现&#xff08…

Mybatis(1)

目录 Mybatis1.快速入门2.Mybatis介绍3.Mybatis工作示意图4.MyBatis 快速入门4.1.1创建monster表4.1.2 创建resources/mybatis-config.xml4.1.3 创建pojo类4.1.4 创建MonsterMapper接口4.1.5 创建MonsterMapper.xml4.1.6 mybatis-config.xml 引入Mapper.xml 文件4.1.6 创建SqIS…

Mysql的页结构详解

1.数据库的存储结构&#xff1a;页 索引结构为我们提供了搞笑的查找方式&#xff0c;索引信息和数据记录都在保存在文件上的&#xff0c;准确地说&#xff0c;是保存在“页”结构中。 1.1磁盘与内存的基本交互单位&#xff1a;页 InnoDB将数据划分为若干个页&#xff0c;Inn…

pycharm中绘制一个3D曲线

import numpy as np import matplotlib.pyplot as plt # 中文的设置 import matplotlib as mp1 from mpl_toolkits.mplot3d import Axes3D mp1.rcParams["font.sans-serif"] ["kaiti"] mp1.rcParams["axes.unicode_minus"] False # 数据创建 X…

超简单的node脚本,将xlsx文件转化为json

开发场景&#xff0c;在一个官网中&#xff0c;官网的设计非常简单&#xff0c;就是一个纯静态的页面&#xff0c;全网站仅一个地方调一下接口&#xff0c;发一下用户填写的信息到运营同学的邮箱&#xff0c;这些数据不会记录在数据库&#xff0c;我需要做一个这样的下拉框。 但…

Clion调试QTQString看不到值问题处理

环境 Clion &#xff1a;2019.3.6 Qt &#xff1a;5.9.6&#xff08;MinGW&#xff09; 环境搭建参考&#xff1a;https://blog.csdn.net/qq_27953479/article/details/132338745 调试时QString看不到值问题处理 下载文件 qt.py : https://github.com/KDE/kdevelop/blob/…

【开源】基于Vue+SpringBoot的康复中心管理系统

项目编号&#xff1a; S 056 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S056&#xff0c;文末获取源码。} 项目编号&#xff1a;S056&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员…

算法基础三

电话号码的字母组合 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "…

轻量封装WebGPU渲染系统示例<41>- 前向渲染的雾(Fog)效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/FogTest.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class FogTest {private mRscene new Rend…

Python标准库copy【侯小啾python领航班系列(十五)】

Python标准库copy【侯小啾python领航班系列(十五)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

uniapp小程序分包页面引入wxcomponents(vue.config.js、copy-webpack-plugin)

实例&#xff1a;小程序添加一个源生小程序插件&#xff0c;按照uniapp官方的说明&#xff0c;要放在wxcomponents。后来发现小程序超2m上传不了。 正常的编译情况 会被编译到主包下 思路&#xff1a;把wxcomponents给编译到分包sub_package下 用uniapp的vue.config.js自定义…

01-使用Git操作本地库,如初始化本地库,提交工作区文件到暂存区和本地库,查看版本信息,版本切换命令等

Git的使用 概述 Git是一个分布式版本控制工具, 通常用来管理项目中的源代码文件(Java类、xml文件、html页面等)进行管理,在软件开发过程中被广泛使用 Git可以记录文件修改的历史记录并形成备份从而实现代码回溯, 版本切换, 多人协作, 远程备份的功能Git具有廉价的本地库,方便…

SeaTunnel扩展Source插件,自定义connector-webservice

代码结构 在seatunnel-connectors-v2中新建connector-webservice模块&#xff0c;可以直接赋值connector-http-base模块&#xff0c;webservice和http的方式比较类似&#xff0c;有些类直接复制了http中的代码。 核心类有WebserviceConfig&#xff0c;WebserviceParameter&am…