Golang协程,通道详解
进程、线程以及并行、并发
关于进程和线程
一个进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
关于并行和并发
多线程程序在单核 CPU 上面运行就是并发多线程程序在多核 CUP 上运行就是并行。

Golang 中的协程(goroutine)以及主线程
Golang 中多协程可以实现并行或者并发。

Goroutine 的使用
package mainimport("fmt""time"
)// 在主线程中也每隔10毫输出"卫宫士郎", 输出2次后,退出程序
// 要求主线程和goroutine同时执行
func test() {for i := 0; i < 10; i++ {fmt.Println("test() 测试专用..........")time.Sleep(time.Millisecond * 100)}
}func main(){go test()for i := 1; i <=2; i++ {fmt.Println("main () 卫宫士郎")time.Sleep(time.Millisecond*10)}}
暴露出一个问题:主线程执行完毕后即使协程没有执行完毕
所以我们对代码进行改造,可以让主线程和协程并行的同时,主线程执行完毕还不会同时带领协程退出运行。
注意:
1、主线程执行完毕后即使协程没有执行完毕程序也会退出
2、协程可以在主线程没有执行完毕前提前退出协程是否执行完毕不会影响主线程的执行为了保证我们的程序可以顺利执行我们想让协程执行完毕后在执行主进程退出。
这个时候我们可以使用sync.WaitGroup 等待协程执行完毕
sync.WaitGroup
package mainimport("fmt""time""sync"
)// 在主线程中也每隔10毫输出"卫宫士郎", 输出2次后,退出程序
// 要求主线程和goroutine同时执行
//主线程退出后所有的协程无论有没有执行完毕都会退出,所以我们在主进程中可以通过WaitGroup等待协程执行完毕
var sw sync.WaitGroupfunc test() {for i := 0; i < 10; i++ {fmt.Println("test() 测试专用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func main(){sw.Add(1) //协程计数器+1go test()//表示开启一个协程for i := 1; i <=2; i++ {fmt.Println("main () 卫宫士郎")time.Sleep(time.Millisecond*10)}sw.Wait() //等待协程执行完毕...fmt.Println("主线程执行完毕、、、、、、")
}
启动多个 Goroutine
package mainimport("fmt""time""sync"
)// 多个协程Goroutine启动var sw sync.WaitGroupfunc test0() {for i := 0; i < 5; i++ {fmt.Println("test0() 测试专用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func test1() {for i := 0; i < 5; i++ {fmt.Println("test1() 测试专用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func main(){sw.Add(1) //协程计数器+1go test0()//表示开启一个协程sw.Add(1)//协程计数器+1go test1()//表示开启一个协程for i := 1; i <=2; i++ {fmt.Println("main () 卫宫士郎")time.Sleep(time.Millisecond*10)}sw.Wait() //等待协程执行完毕...fmt.Println("主线程执行完毕、、、、、、")
}
设置 Golang 并行运行的时候占用的 cup 数量
package mainimport ("fmt""runtime"
)func main() {//获取当前计算机上面的Cup个数cpuNum := runtime.NumCPU()fmt.Println("cpuNum=", cpuNum)//可以自己设置使用多个cpuruntime.GOMAXPROCS(cpuNum - 1)fmt.Println("设置完成")
}//cpuNum= 8
//设置完成
来求一个素数的操作如下:
package mainimport ("fmt""time"
)func main() {start := time.Now().Unix()fmt.Println(start)for num := 2; num < 10; num++ {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {fmt.Println(num, "是素数")}}end := time.Now().Unix()fmt.Println(end)fmt.Println(end-start) }
goroutine for循环实现
package mainimport ("fmt""sync""time"
)//需求:要统计1-120000的数字中那些是素数?goroutine for循环实现/*
1 协程 统计 1-300002 协程 统计 30001-600003 协程 统计 60001-900004 协程 统计 90001-120000// start:(n-1)*30000+1 end:n*30000
*/
var wg sync.WaitGroupfunc test(n int) {for num := (n-1)*30000 + 1; num < n*30000; num++ {if num > 1 {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {// fmt.Println(num, "是素数")}}}wg.Done()
}func main() {for i := 1; i <= 4; i++ {wg.Add(1)go test(i)}wg.Wait()fmt.Println("执行完毕")}
Channel 管道
channel
单纯地将函数并发执行是没有意义的。
函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。
channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
channel类型
channel是一种类型,一种引用类型。声明通道类型的格式如下:
var 变量 chan 元素类型
举几个例子:
var ch1 chan int // 声明一个传递整型的通道var ch2 chan bool // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道
创建channel
通道是引用类型,通道类型的空值是nil。
var ch chan int
fmt.Println(ch) // <nil>
package mainimport "fmt"func main() {ch1 := make(chan int ,4)ch1<- 1ch1<- 2ch1<- 3ch2 := ch1ch2<-4<-ch1<-ch1<-ch1d:= <-ch1fmt.Println(d)
}//4
副本ch2的值添加后,取出ch1的值改变了
声明的通道后需要使用make函数初始化之后才能使用。
创建channel的格式如下:
make(chan 元素类型, [缓冲大小])
channel的缓冲大小是可选的。
举几个例子:
//创建一个能存储 10 个 int 类型数据的管道
ch1 := make(chan int, 10)
//创建一个能存储 4 个 bool 类型数据的管道
ch2 := make(chan bool, 4)
//创建一个能存储 3 个[]int 切片类型数据的管道
ch3 := make(chan []int, 3)
package mainimport "fmt"func main() {//创建channelch := make(chan int, 3)//2、给管道里面存储数据ch <- 12ch <- 33ch <- 3234//获取管道里面的内容a := <-chfmt.Println(a) //12<-ch //从管道里面取值 //33c := <-chfmt.Println(c) //3234ch <- 1ch <- 22//打印管道的长度和容量fmt.Printf("值:%v 容量:%v 长度%v\n", ch, cap(ch), len(ch))
}
已经消费了的,就相当于没有,再添加的从新算
channel操作
通道有发送(send)、接收(receive)和关闭(close)三种操作。
发送和接收都使用<-符号。
现在我们先使用以下语句定义一个通道:
ch := make(chan int)
发送
将一个值发送到通道中。
ch <- 10 // 把10发送到ch中
接收
从一个通道中接收值。
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
关闭
我们通过调用内置的close函数来关闭通道。
close(ch)
关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。
关闭后的通道有以下特点:
1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。
管道阻塞
无缓冲的通道
package main
import ("fmt"
)func main() {ch := make(chan int)ch <- 123fmt.Println("传递成功......")
}
上面这段代码能够通过编译,但是执行的时候会出现以下错误:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:8 +0x31
exit status 2
为什么会出现死锁
因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有接收值的时候才能发送值。(小区没代收快递点,需要快递小哥直接送到手上)
上面的代码会阻塞在ch <- i这一行代码形成死锁
因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。
上面的代码会阻塞在ch <- 123这一行代码形成死锁,那如何解决这个问题呢?
一种方法是启用一个goroutine去接收值,例如:
func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}
func main() {ch := make(chan int)go recv(ch) // 启用goroutine从通道接收值ch <- 10fmt.Println("发送成功")
}
无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。
有缓冲的通道
解决上面问题的方法还有一种就是使用有缓冲区的通道。
package main
import ("fmt"
)// func recover(ch chan int){
// rec := <- ch
// fmt.Println("接收成功",rec)
// }func main() {ch := make(chan int,1)// go recover(ch)ch <- 123fmt.Println("传递成功......")
}
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。(小区快递格子就一个,你取走了,别人能再放)
循环遍历管道数据
循环的话,我们就会提到for,但是for有两种循环形式
for range 和 for 用两种方式来操作
for range循环遍历管道的值 ,注意:管道没有key
package mainimport "fmt"func main() {ch1 := make(chan int,5)for i := 1; i <= 5; i++ {ch1 <- i}for v := range ch1 {fmt.Println(v)}}
我们发现虽然可以正常编译,运行,但是会出现如下情况:
1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:14 +0xb4
exit status 2
这样也会产生死锁,使用for range遍历通道,当通道被关闭的时候就会退出for range,如果没有关闭管道就会报错fatal error: all goroutines are asleep - deadlock!
如果通过for range循环的方式来从管道取数据,在插入数据的时候一定要close()
package main
import ("fmt"
)func main() {var ch1 = make(chan int, 5)for i := 1; i <= 5; i++ {ch1 <- i}close(ch1) //关闭管道//for range循环遍历管道的值 ,注意:管道没有keyfor v := range ch1 {fmt.Println(v)}
}
通过内置的close()函数关闭channel(如果你的管道不往里存值或者取值的时候一定记得关闭管道)
第二种方法
package main
import ("fmt"
)func main() {//通过for循环遍历管道的时候管道可以不关闭var ch2 = make(chan int, 5)for i := 1; i <= 5; i++ {ch2 <- i}for j := 0; j < 5; j++ {fmt.Println(<-ch2)}
}
并发安全和锁
有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。
互斥锁
package mainimport ("fmt""sync""time"
)var count = 0
var sw sync.WaitGroupvar mutex sync.Mutexfunc test() {mutex.Lock()count++fmt.Println("the count is : ", count)time.Sleep(time.Millisecond)mutex.Unlock()sw.Done()
}func main() {for r := 0; r < 20; r++ { //开启20个协程来进行这个操作wg.Add(1)go test()}sw.Wait()}
使用互斥锁能够保证同一时间有且只有一个 goroutine 进入临界区,其他的 goroutine 则在等 待锁;当互斥锁释放后,等待的 goroutine 才可以获取锁进入临界区,多个 goroutine 同时等待一个锁时,唤醒的策略是随机的。虽然使用互斥锁能解决资源争夺问题,但是并不完美,通过全局变量加锁同步来实现通讯,并不利于多个协程对全局变量的读写操作。这个时候我们也可以通过另一种方式来实现上面的功能管道(Channel)
读写互斥锁
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。
读写锁在Go语言中使用sync包中的RWMutex类型。
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;
当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
package mainimport("fmt""sync""time"
)var (x int64wg sync.WaitGrouplock sync.Mutexrwlock sync.RWMutex
)func write() {// lock.Lock() // 加互斥锁rwlock.Lock() // 加写锁x = x + 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒fmt.Println("=========进行写操作")rwlock.Unlock() // 解写锁// lock.Unlock() // 解互斥锁wg.Done()
}func read() {// lock.Lock() // 加互斥锁rwlock.RLock() // 加读锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒fmt.Println("=========进行读操作")rwlock.RUnlock() // 解读锁// lock.Unlock() // 解互斥锁wg.Done()
}func main() {for i := 0; i < 3; i++ {wg.Add(1)go write()}for i := 0; i < 10; i++ {wg.Add(1)go read()}wg.Wait()}/**/
(也就是说,当一个 goroutine 进行写操作的时候,其他 goroutine 既不能进行读操作,也不能进行写操作)
Goroutine 结合 Channel 管道
需求 1:
1 、开启一个 fn1 的的协程给向管道 inChan 中写入 100 条数据2 、开启一个 fn2 的协程读取 inChan 中写入的数据3 、注意: fn1 和 fn2 同时操作一个管道4 、主线程必须等待操作完成后才可以退出
package mainimport ("fmt""sync""time"
)
//这是一个无缓存通道案例
//定义sync等待协程完毕
var wg sync.WaitGroupfunc fn1(intChan chan int) {for i := 0; i < 10; i++ {intChan <- i + 1fmt.Println("写入数据=", i+1)time.Sleep(time.Millisecond * 100)}close(intChan) //写入操作完毕,关闭写入的协程wg.Done()
}
func fn2(intChan chan int) {for v := range intChan { //通道回显只有一个值fmt.Printf("读到数据=%v\n", v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan := make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println("读取完毕...")
}
需求 2:
goroutine 结合 channel 实现统计 1-120 的数字中那些是素数?
package mainimport("fmt""sync"
)var sw sync.WaitGroup
//向 intChan放入 1-120个数,创建协程
func putNum(intChan chan int ){for i := 0; i < 120; i++ {intChan <- i}close(intChan)sw.Done()
}// 从 intChan取出数据,并判断是否为素数,如果是,就把得到的素数放在primeChanfunc primeNum(intChan chan int,primeChan chan int, exitChan chan bool ){for num := range intChan {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {primeChan <- num //num是素数}
}//要关闭 primeChan// close(primeChan) //如果一个channel关闭了就没法给这个channel发送数据了//什么时候关闭primeChan?//给exitChan里面放入一条数据exitChan <- true sw.Done()}//printPrime打印素数的方法
func printPrime(primeChan chan int) {for v := range primeChan {fmt.Println(v)}sw.Done()
}func main(){intChan := make(chan int,1000) //在intchan中放入数字primeChan := make(chan int,1000) //从 intChan取出数据,判断是否是素数exitChan := make(chan bool ,20) //标识primeChan close,内部数据满足设定的缓存数量就关闭//存放数字的协程sw.Add(1)go putNum(intChan)//统计素数的协程for i := 0; i < 20; i++ { //你要开启几个primechan的协程就写几个,对应的exitchan要一致sw.Add(1)go primeNum(intChan ,primeChan , exitChan )}//打印素数的协程sw.Add(1)go printPrime(primeChan)//判断exitChan是否存满值sw.Add(1)go func() {for i := 0; i < 20; i++ {<-exitChan}close(primeChan) //关闭primeChansw.Done()}()sw.Wait()fmt.Println("执行完毕....")}
单向管道
package mainimport "fmt"//单向管道
func main() {// 1、在默认情况下下,管道是双向ch := make(chan int, 2)ch <- 1ch <- 2a := <-chb := <-chfmt.Println(a, b) //1,2// 2、管道声明为只写ch1 := make(chan<- int, 2)ch1 <- 10ch1 <- 12// <-ch1 //receive from send-only type chan<- int// 3、管道声明为只读ch2 := make(<-chan int, 2)ch2 <- 3c := <-ch2fmt.Println(c) //.\main.go:25:2: invalid operation: cannot send to receive-only channel ch2 (variable of type <-chan int)}
修改之前的案例如下:
package mainimport ("fmt""sync""time"
)
//这是一个无缓存通道案例
//定义sync等待协程完毕
var wg sync.WaitGroupfunc fn1(intChan chan<- int) {for i := 0; i < 10; i++ {intChan <- i + 1fmt.Println("写入数据=", i+1)time.Sleep(time.Millisecond * 100)}close(intChan) //写入操作完毕,关闭写入的协程wg.Done()
}
func fn2(intChan <-chan int) {for v := range intChan { //通道回显只有一个值fmt.Printf("读到数据=%v\n", v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan := make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println("读取完毕...")
}/*
写入数据= 1
读到数据=1
写入数据= 2
读到数据=2
写入数据= 3
读到数据=3
写入数据= 4
读到数据=4
写入数据= 5
读到数据=5
写入数据= 6
读到数据=6
写入数据= 7
读到数据=7
写入数据= 8
读到数据=8
写入数据= 9
读到数据=9
写入数据= 10
读到数据=10
读取完毕...
*/
select 多路复用
在某些场景下我们需要同时从多个通道接收数据,这个时候就可以用到golang中给我们提供的select多路复用
如果只想在main方法内进行,就可以用这个方法,其他的就是定义协程了
使用select来获取channel里面的数据的时候不需要关闭channel
package mainimport("fmt""time"
)func main(){
// 在某些场景下我们需要同时从多个通道接收数据,这个时候就可以用到golang中给我们提供的select多路复用//如果只想在main方法内进行,就可以用这个方法,其他的就是定义协程了//1.定义一个管道 10个数据int
intoChan := make(chan int ,10)
for i := 0; i < 10; i++ {intoChan <- i
}
//2.定义一个管道 5个数据string
stringChan := make(chan string,5)
for i := 0; i < 5; i++ {stringChan <- "卫宫士郎"
}
//定义一个for的无限循环
for{select{case value := <- intoChan:fmt.Printf("从 intChan 读取的数据%d\n", value)case value := <-stringChan:fmt.Printf("从 stringChan 读取的数据%v\n", value)time.Sleep(time.Millisecond * 50)default:fmt.Printf("数据获取完毕")return //注意退出...}
}}/*
从 stringChan 读取的数据卫宫士郎
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据0
从 intChan 读取的数据1
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据2
从 intChan 读取的数据3
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据4
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据5
从 intChan 读取的数据6
从 intChan 读取的数据7
从 intChan 读取的数据8
从 intChan 读取的数据9
数据获取完毕*/
Goroutine Recover 解决协程中出现的 Panic
defer + recover
延迟执行(定义的func自执行函数出现问题就交给defer)其他的协程还可以继续进行
package mainimport ("fmt""time"
)//函数
func test0() {for i := 0; i < 10; i++ {time.Sleep(time.Millisecond * 50)fmt.Println("远坂凛")}
}//函数
func test1() {//这里我们可以使用defer + recover //延迟执行(定义的func自执行函数出现问题就交给defer)//其他的协程还可以继续进行defer func() {//捕获test抛出的panicif err := recover(); err != nil {fmt.Println("test1() 发生错误", err)}}()//定义了一个mapvar myMap map[int]stringmyMap[0] = "golang" //error}func main() {go test0()go test1()//防止主进程退出这里使用time.Sleep演示,搭建也可以用sync.WaitGrouptime.Sleep(time.Second)
}
注意,调用recover()
来捕获 goroutine 恐慌只在一个defer
函数内部有用;否则,该函数将返回nil
并且没有其他作用。这是因为defer
函数也是在周围函数恐慌时执行的。
在 Go 中,panic
是一个停止普通流程的内置函数:
func main() {fmt.Println("a")panic("foo")fmt.Println("b")
}
该代码打印a
,然后在打印b
之前停止:
a
panic: foogoroutine 1 [running]:
main.main()main.go:7 +0xb3
一旦恐慌被触发,它将继续在调用栈中向上运行,直到当前的 goroutine 返回或者panic
被recover
捕获:
func main() {defer func() { // ❶if r := recover(); r != nil {fmt.Println("recover", r)}}()f() // ❷
}func f() {fmt.Println("a")panic("foo")fmt.Println("b")
}
❶ 延迟闭包内调用recover
❷ 调用f
,f
恐慌。这种恐慌被前面的recover
所抓住。
在f
函数中,一旦panic
被调用,就停止当前函数的执行,并向上调用栈:main
。在main
中,因为恐慌是由recover
引起的,所以并不停止 goroutine:
a
recover foo
相关文章:

Golang协程,通道详解
进程、线程以及并行、并发 关于进程和线程 进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一…...

unity 之 Vector 数据类型
文章目录 Vector 1Vector 2Vector 3Vector 4 Vector 1 在Unity中,Vector1 并不是一个常见的向量类型。 如果您需要表示标量(单个值)或者只需要一维的数据,通常会直接使用浮点数(float)或整数(in…...

私密数据采集:隧道爬虫IP技术的保密性能力探究
作为一名专业的爬虫程序员,今天要和大家分享一个关键的技术,它能够为私密数据采集提供保密性能力——隧道爬虫IP技术。如果你在进行敏感数据采集任务时需要保护数据的私密性,那么这项技术将是你的守护神。 在进行私密数据采集任务时ÿ…...

使用git rebase 之后的如何恢复到原始状态
我们常常喜欢使用git rebase去切换分支提交代码,操作流程就是: 先切换分支:比如当前是master 我们修改了一堆代码产生一个commit id :5555555567777 那么我们常常比较懒就直接切换了:git checkout dev 然后呢?使用命令git rebase 5555555567777,想把这笔修改提交到d…...

matlab相机标定知识整理
matlab相机标定知识整理 单目相机标定 单目相机标定 内参矩阵:cameraParams.Intrinsics.K 或者 cameraParams.K旋转矩阵:cameraParams.RotationMatrices 有待确定 cameraParams.RotationVectors平移矩阵:cameraParams.TranslationVectors径向…...

win11安装ubuntu 子系统安装过程及注意事项
第一步 :安装系统必须组件 由于子系统是系统自带组件,需要安装软件支持 第二步:应用商店安装 ubuntu 编辑 编辑 这个时候打开会报错 第三步,运行linux子系统 选择Windows PowerShell 以管理员身份运行) 输入&#…...

torch.cat((A,B),dim=1)解析
官方说明torch.cat 引用自:Pytorch中的torch.cat()函数 torch.cat(tensors, dim0, *, outNone) → Tensor # 连接给定维数的给定序列的序列张量。所有张量要么具有相同的形状(除了连接维度),要么为空。示例 输入: import torch a torch.Tens…...

apache配置安全证书https踩坑记录
apache配置安全证书有如下几步 一、申请证书 这个网上有很多免费的,我用的是阿里云的服务器,在阿里云后台就可以申请免费证书。 二、上传证书 申请好证书后,根据服务器用的什么软件,是apache还是ngnix,下载相应的证书…...

SQL Server Express 自动备份方案
文章目录 SQL Server Express 自动备份方案前言方案原理SQL Server Express 自动备份1.创建存储过程2.设定计划任务3.结果检查sqlcmd 参数说明SQL Server Express 自动备份方案 前言 对于许多小型企业和个人开发者来说,SQL Server Express是一个经济实惠且强大的数据库解决方…...

Docker资源控制
目录 一、CPU 资源控制 1.设置CPU使用率上限 2.设置CPU资源占用比(设置多个容器时才有效) 3.设置容器绑定指定的CPU 二、对内存使用的限制 三、对磁盘IO配额控制(blkio)的限制 一、CPU 资源控制 cgroups,是一个非常强…...

微服务中间件-分布式缓存Redis
分布式缓存 a.Redis持久化1) RDB持久化1.a) RDB持久化-原理 2) AOF持久化3) 两者对比 b.Redis主从1) 搭建主从架构2) 数据同步原理(全量同步)3) 数据同步原理(增量同步) c.Redis哨兵1) 哨兵的作用2) 搭建Redis哨兵集群3) RedisTem…...

java面试强基(16)
目录 clone方法的保护机制 Java中由SubString方法是否会引起内存泄漏? Java中提供了哪两种用于多态的机制? 程序计数器(线程私有) 如何判断对象是否是垃圾? clone方法的保护机制 clone0方法的保护机制在Object中是被声明为 protected的。以User…...

Python可视化在量化交易中的应用(13)_Seaborn直方图
Seaborn中带核密度的直方图的绘制方法 seaborn中绘制直方图使用的是sns.histlot()函数: sns.histplot(data,x,y,hue,weights,stat‘count’,bins‘auto’,binwidth,binrange,discrete,cumulative,common_bins,common_norm,multiple‘layer’,element‘bars’,fill,…...

NOIP 2006 普及组 第二题 开心的金明
开心的金明 说明 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。 更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超…...

「UG/NX」Block UI 指定点SpecifyPoint
✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#...

Linux Shell如果ping失败就重启网卡(详解)
直接上脚本 -------------------------------------------------------------------------- #vi /tmp/ping_check.sh #!/bin/bash IP="1.1.1.1" PacketLoss=`ping -c 4 -w 4 1.1.1.1 | grep packet loss | awk -F packet loss {print $1} | awk {print $NF}|se…...

每天一道leetcode:剑指 Offer 13. 机器人的运动范围(中等广度优先遍历剪枝)
今日份题目: 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之…...

TypeError: a bytes-like object is required, not ‘str‘
raceback (most recent call last): File "D:\pycharmcode\client.py", line 12, in <module> tcp_socket.send(send_data) TypeError: a bytes-like object is required, not str 使用socket进行ubuntu与windows通信时,发送数据时报了以上错…...

题解 | #1005.List Reshape# 2023杭电暑期多校9
1005.List Reshape 签到题 题目大意 按一定格式给定一个纯数字一维数组,按给定格式输出成二维数组。 解题思路 读入初始数组字符串,将每个数字分离,按要求输出即可 参考代码 参考代码为已AC代码主干,其中部分功能需读者自行…...

会声会影2023旗舰版电脑端视频剪辑软件
随着短视频、vlog等媒体形式的兴起,视频剪辑已经成为了热门技能。甚至有人说,不会修图可以,但不能不会剪视频。实际上,随着各种智能软件的发展,视频剪辑已经变得越来越简单。功能最全的2023新版,全新视差转…...

【linux基础(四)】对Linux权限的理解
💓博主CSDN主页:杭电码农-NEO💓 ⏩专栏分类:Linux从入门到开通⏪ 🚚代码仓库:NEO的学习日记🚚 🌹关注我🫵带你学更多操作系统知识 🔝🔝 Linux权限 1. 前言2. shell命…...

maven项目指定数据源
springboot项目 直接在pom.xml文件中添加以下配置 <!--使用阿里云maven中央仓库--> <repositories><repository><id>aliyun-repos</id><url>http://maven.aliyun.com/nexus/content/groups/public/</url><snapshots><ena…...

web3:使用Docker-compose方式部署blockscout
最近做的项目,需要blockscout来部署一个区块链浏览器,至于blockscout是什么,咱们稍后出一篇文章专门介绍下,本次就先介绍一下如何使用Docker-compose方式部署blockscout,以及过程中遇到的种种坑 目录 先决条件我的环境准备工作Docker-compose1.安装方式一:下载 Docker Co…...

C++11实用技术(五)泛型编程加载dll接口函数
C11泛型编程简化加载dll代码 常见的加载dll方式: HMODULE m_hDataModule; m_hDataModule LoadLibrary("myDll.dll");typedef int (*PfunA)(int a, int b);//定义函数指针 PfunA fun (PfunA)(GetProcAddress(m_hDataModule , "funA"));//加载…...

使用wxPython和PyMuPDF提取PDF页面指定页数的内容的应用程序
在本篇博客中,我们将探讨如何使用wxPython和PyMuPDF库创建一个简单的Bokeh应用程序,用于选择PDF文件并提取指定页面的内容,并将提取的内容显示在文本框中。 C:\pythoncode\new\pdfgetcontent.py 准备工作 首先,确保你已经安装了…...

k8s的pv和pvc创建
//NFS使用PV和PVC 1、配置nfs存储 2、定义PV 实现 下图的pv和pvc测试 pv的定义 这里定义5个PV,并且定义挂载的路径以及访问模式,还有PV划分的大小 vim /pv.yamlapiVersion: v1 kind: PersistentVolume metadata:name: pv001 spec:capacity:storage: …...

记K8S集群工作节点,AnolisOS 8.6部署显卡驱动集成Containerd运行时
1、安装gcc #安装编译环境 yum -y install make gcc gcc-c2、下载显卡驱动 点击 直达连接 nvidia高级搜索下载历史版本驱动程序(下载历史版本驱动) https://www.nvidia.cn/Download/Find.aspx?langcn3、安装驱动 安装显卡驱动 ./NVIDIA-Linux-x86…...

JavaScript 性能优化
优化JavaScript代码的性能是开发过程中的一个关键任务,它可以显著提升网站或应用的用户体验。以下是一些优化技巧,涵盖了减少重绘、减少内存占用和合并网络请求等方面: 1. **减少重绘和重排:** - **使用 CSS3 动画:…...

架构演进及常用架构
1架构演进及常用架构 1.1单体分层架构 1.2 多应用微服务架构 1.3 分布式集群部署 部署 CDN 节点: 用户访问量的增加意味着用户地域的分散请求,如果所有请求都直接发送中心服务器的话,距离越远,响应速度越差,这时就需…...

WinCC V7.5 中的C脚本对话框不可见,将编辑窗口移动到可见区域的具体方法
WinCC V7.5 中的C脚本对话框不可见,将编辑窗口移动到可见区域的具体方法 由于 Windows 系统更新或使用不同的显示器,在配置C动作时,有可能会出现C脚本编辑窗口被移动到不可见区域的现象。 由于该窗口无法被关闭,故无法进行进一步…...