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 := 1for 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 intintChan = make(chan int,3)//2.看看intChan是什么fmt.Printf("intchan的值是=%v intChan本身的地址=%p\n",intChan,&intChan)//3.像管道写入数据intChan<-10num := 211intChan<- num//注意点,当我们在给管道写入数据时,不能超过其容量intChan<- 50//intChan<- 98 //会报错//4.输出看看管道的长度和cap(容量)fmt.Printf("channel len =%v cap=%v\n",len(intChan),cap(intChan)) // 3,3//5.从管道中读取数据var num2 intnum2 = <-intChanfmt.Printf("num2=%v\n",num2) //10fmt.Printf("channel len =%v cap=%v\n",len(intChan),cap(intChan))//2,3//6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlocknum3 := <-intChannum4 := <-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 stringAge int
}func main() {//定义一个存放任意数据类型的管道3个数据// var  allChan chan interface{}allChan := make(chan interface{},3)allChan<-10allChan<-"tom jack"cat :=Cat{"小花猫",4}allChan<- cat//我们希望获得管道中的第三个元素,则先将前2个推出
<-allChan
<-allChannewCat :=<-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<- 100intChan<- 200close(intChan) //close//这时不能够再写入到数channel//intChan<- 300 //panic: send on closed channelfmt.Println("okok~")//当管道关闭后,读取数据是可以的n1 := <-intChanfmt.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<- 100intChan<- 200close(intChan) //close//这时不能够再写入到数channel//intChan<- 300 //panic: send on closed channelfmt.Println("okok~")//当管道关闭后,读取数据是可以的n1 := <-intChanfmt.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<- ifmt.Println("writeData",i)// time.Sleep(time.Second )}close(intChan)//关闭管道,不影响读
}readDtata
func readData(intChan chan int,exitChan chan bool) {for {v,ok := <-intChanif !ok {break}//time.Sleep(time.Second )fmt.Printf("readData 读到的数据=%v\n",v)}//readData 读取完数据后,即任务完成exitChan<- true //数据读取完之后就网退出管道加入一个1close(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 :=<-exitChanif !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}//关闭intChanclose(intChan)
}//从intchan中取出数据,并判断是否为素数,如果是就放入到primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){//使用for循环var flag boolfor {time.Sleep(time.Millisecond)num,ok := <-intChanif !ok { //intChan取不到的时候,就退出这个主for循环break}flag = true //假设是素数//判断num是不是素数for i :=2;i<num;i++{if num %i ==0 { //说明i不是素数flag = falsebreak}}if flag {//将这个数就放入到primeChan中primeChan<- num}}fmt.Println("有一个协程因为取不到数据没退出!")//这里我们还不能关闭primeChan//向exitChan 写入trueexitChan<- 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中取出数据,并判断是否为素数,如果是就放入到primeChanfor i :=0;i<4; i++{go primeNum(intChan,primeChan,exitChan)}//这里我们主线程,进行处理go func() {for i :=0;i<4; i++{<-exitChan}//当我们从exitChan祛除了4个结果,就可以放心关闭primeChanclose(primeChan)}()//遍历primeChanfor {res,ok := <-primeChanif !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<- intchan2 = make(chan int,3)chan2<- 20// num := <-chan2 err在这个管道中不可以读fmt.Println("chan2=",chan2)//3.声明为只读var chan3 <-chan intnum2 := <-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 := <-chif !ok {break}fmt.Println(v)}var a struct{}exitChan <- a
}
func main() {var ch chan intch = make(chan int , 10)exitChan :=make(chan struct{},2)go send(ch,exitChan)go recv(ch,exitChan)var total = 0for _= range exitChan {total ++if total == 2 {break}}fmt.Println("结束...")
}
3)使用select可以解决从管道取数据的阻塞问题
package  main
import ("fmt""time"
)
func main() {//使用select可以解决从管道读取数据阻塞问题//1.先定义一个管道 10个数据 intintChan :=make(chan int, 10)for i := 0 ; i < 10 ;i ++{intChan<- i}//2.定义一个管道5个数据stringstringChan :=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,会自动到下一个casefmt.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)returnbreak 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 + recoverdefer func() {//捕获test爬出的panicif err := recover(); err !=nil {fmt.Println("test()发生错误",err)}}()//定义了一个mapvar myMap map[int]stringmyMap[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,进行处理,这些即使这个协程发生的问题,但是主线程任然不受影响,可以继续运行

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

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

相关文章

MySQL 8 update语句更新数据表里边的数据

数据重新补充 这里使用alter table Bookbought.bookuser add userage INT after userphone;为用户表bookuser在userphone列后边添加一个类型为INT的新列userage。 使用alter table Bookbought.bookuser add sex varchar(6) after userage ;为用户表bookuser在userage 列后边添…

Oracle-数据库连接数异常上涨问题分析

问题&#xff1a; 用户的数据库在某个时间段出现连接数异常上涨问题&#xff0c;时间持续5分钟左右&#xff0c;并且问题期间应用无法正常连接请求数据库 从连接数的监控上可以看到数据库平常峰值不到100个连接&#xff0c;在问题时间段突然上涨到400以上 问题分析&#xff1a;…

unity | 动画模块之循环滚动选项框

一、作者的话 评论区有人问&#xff0c;有没有竖排循环轮播选项框&#xff0c;我就写了一个 二、效果动画 如果不是你们想要的&#xff0c;就省的你们继续往下看了 三、制作思路 把移动分成里面的方块&#xff0c;还有背景&#xff08;父物体&#xff09;&#xff0c;方块自…

网络模拟与网络仿真

目录 一、概念界定 二、模拟&#xff08;simulation&#xff09;与仿真&#xff08;emulation&#xff09; 2.1 模拟&#xff08;simulation&#xff09; 2.2 仿真&#xff08;emulation&#xff09; 2.3 区分 三、网络模拟与网络仿真 3.1 网络模拟 3.2 网络仿真 3.…

【算法】算法题-20231206

这里写目录标题 一、非自身以外数字的乘积二、最大数三、奇数排序 一、非自身以外数字的乘积 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀…

VA05销售报表屏幕增强

业务需求&#xff1a;在VA05报表界面增强两个字段&#xff08;BELNR1/BELNR2&#xff09;. 第一步&#xff1a;扩展VA05相关表结构 由于新增的字段是按照销售订单行维度展示的&#xff0c;所以本篇加在VBAP表里&#xff08;不扩展表字段&#xff0c;直接写增强&#xff0c;会…

融云 Global IM UIKit,灵活易用的即时通讯组件设计思路和最佳实践

&#xff08;全网都在找的《社交泛娱乐出海作战地图》&#xff0c;点击获取&#x1f446;&#xff09; 融云近期推出的 Global IM UIKit&#xff0c;支持开发者高效满足海外用户交互体验需求&#xff0c;且保留了相当的产品张力赋予开发者更多自由和灵活性&#xff0c;是实现全…

现货黄金会面临哪些风险?

进行现货黄金投资&#xff0c;我们除了要了解怎么找到交易机会以外&#xff0c;也要知道我们交易会面临哪些风险&#xff0c;了解风险就是做到知己知彼&#xff0c;了解风险才能控制风险。控制住风险&#xff0c;才能为我们稳定盈利打好基础&#xff0c;那么下面我们就来看看在…

ESP32-Web-Server编程-在网页中插入图片

ESP32-Web-Server编程-在网页中插入图片 概述 图胜与言&#xff0c;在网页端显示含义清晰的图片&#xff0c;可以使得内容更容易理解。 需求及功能解析 本节演示在 ESP32 Web 服务器上插入若干图片。在插入图片时还可以对图片设置一个超链接&#xff0c;用户点击该图片时&a…

Oracle merge into语句(merge into Statement)

在Oracle中&#xff0c;常规的DML语句只能完成单一功能&#xff0c;&#xff0c;例如insert/delete/update只能三选一&#xff0c;而merge into语句可以同时对一张表进行更新/插入/删除。 目录 一、基本语法 二、用法示例 2.1 同时更新和插入 2.2 where子句 2.3 delete子句 2.4…

Gitee项目推荐-HasChat

最近由于使用的局域网通信工具总是出问题&#xff0c;就在考虑有没有好的替代品。搜索了一番&#xff0c;发现这个还不错&#xff1a; HasChat: 一款极简聊天应用&#xff0c;比较完整&#xff0c;略好看 页面简洁&#xff0c;功能也比较齐全&#xff0c; 感兴趣的小伙伴可以…

【Redis】redis 高性能--线程模型以及epoll网络框架

目录 一.前言 二.多线程的弊端 2.1 锁的开销问题 2.2 多线程上下文切换带来的额外开销 2.3 多线程占用内存成本增高 三.基本IO模型与epoll 模式 3.1 基本IO模型 3.2 单线程处理机制 四.总结 一.前言 我们经常讨论到&#xff0c;redis 是单线程&#xff0c;那为什么单线…

sizeof()、strlen()、length()、size()的区别(笔记)

​ 上面的笔记有点简陋&#xff0c;可以看一下下面这个博主的&#xff1a; c/c中sizeof()、strlen()、length()、size()详解和区别_csize,sizeof,length_xuechanba的博客-CSDN博客

the name of a constructor must match the name of the enclosing class

构造器名匹配封闭类名 命令码的位置关系不对 解决&#xff1a;调整 命令码所在层级

xxljob学习笔记02(小滴课堂)

分布式调度参数传递和调度日志配置讲解 可以设置任务参数。 代码层面&#xff1a; 可以这样传递参数。 我们在xxljob页面去设置参数&#xff1a; 我们执行一次任务&#xff1a; 我们这里就拿到了参数。 这样我们就能拿到参数了。 日志打印&#xff1a; 在代码中也可以实现&…

GPT-Crawler一键爬虫构建GPTs知识库

GPT-Crawler一键爬虫构建GPTs知识库 写在最前面安装node.js安装GPT-Crawler启动爬虫结合 OpenAI自定义 assistant自定义 GPTs&#xff08;笔者用的这个&#xff09; 总结 写在最前面 GPT-Crawler一键爬虫构建GPTs知识库 能够爬取网站数据&#xff0c;构建GPTs的知识库&#xf…

npm : 无法加载文件 D:\nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本。

今天在使用vscode下载项目的依赖时&#xff0c;输入 pnmp install,结果报错: npm : 无法加载文件 D:\nodejs\node_global\npm.ps1&#xff0c;因为在此系统上禁止运行脚本。原因&#xff1a; 因为在此系统上禁止运行脚本&#xff0c;也就是说没有权限&#xff0c;查一下&#…

CoreDNS实战(十一)-分流与重定向

本文主要介绍了目前CoreDNS服务在外部域名递归结果过程中出现的一些问题以及使用dnsredir插件进行分流和alternate插件进行重试优化的操作。 1 自建DNS服务现状 一般来说&#xff0c;无论是bind9、coredns、dnsmasq、pdns哪类dns服务器&#xff0c;我们自建的监听在UDP53端口…

AI 绘画 | Stable Diffusion LCM和FP8 显存不足的福音

前言 在我们使用Stable Diffusion 作画的时候,普通用户因为电脑显存配置过低,经常会出现爆显存和出图慢的困扰。而SD-WebUI在显存优化方便不如ComfyUI和Fooocus,但是也有一些弥补SD-WebUI显存问题的方案,那就是LCM和FP8。 LCM 教程 简介 LCM 是一个用于 Stable Diffusio…

[组合数学]LeetCode:2954:统计感冒序列的数目

作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 题目 给你一个整数 n 和一个下标从 0 开始的整数数组 sick &#xff0c;数组按 升序 排序。 有 n 位小朋友站成一排&#xff0c;按顺序编号为 0 到 n - 1 。数组 sick 包含一开始得了感冒的小朋友的位置。如果位…