当前位置: 首页 > article >正文

Go红队开发—并发编程

文章目录

  • 并发编程
    • go协程
      • chan通道
      • 无缓冲通道
      • 有缓冲通道
      • 创建⽆缓冲和缓冲通道
    • 等协程
      • sync.WaitGroup同步
      • Runtime包
        • Gosched()
        • Goexit()
      • 区别
    • 同步变量
      • sync.Mutex互斥锁
      • atomic原子变量
    • Select
    • Ticker定时器
    • 控制并发数量
      • 核心机制
  • 并发编程阶段练习
    • 重要的细节
    • 端口扫描
    • 股票监控

并发编程

go协程

chan通道

必要条件:
从通道取元素的时候要先关闭通道,程序才知道你不往通道输入了,才能取出元素来进行操作,否则会由于通道没有被关闭,range 操作会一直等待新的数据到来,导致程序陷入死锁状态。

close关闭通道不是必要的,但是如果你不关闭,还一直读取里面的东西的话, 你要保证你有源源不断的内容进入到通道中不能让他处于死锁状态。

//在go中的通道
var 变量名 = make(chan 类型)
var c = make(chan int)
//通道可以给很多数据,取出来的时候可以一个个的pop一样,每次取一个就pop一个
c <- 123
c <- 456
close(c) //你要取出内容的时候要先关闭通道,程序才知道你不要往通道输入了,要打印了。否则打印失败,因为一直处于等待状态会死锁。
for v := range c{fmt.Println(v)  //这里会依次打印123,456
}

个人理解:

0.go协程感觉可以理解为比线程更加细的,同时也更快的一种东西,它能够实现多线程操作
(其实应该说多协程操作,就是能同时干很多事情)1.go的协程main运行结束前他能够做抢占运行权的能力,但是一旦main运行结束他也就寄寄了。2.`通道(Channels)`:这个很重要,在协程中如果你没有用缓冲通道,就代表你使用go协程的话,能够进到协程里面运行的概率比较小,因为main一直抢占执行权,如果很快就执行完main的话就没go协程啥事情了。3.缓冲区:当你在使用Channels的时候且使用⽆缓冲通道,如果造成了阻塞->那么就是你要取通道的东西,但是需要等通道有东西取的时候才能往下走程序。
可如果你make有缓冲通道的时候,就不会造成阻塞的状态,前提是拿缓冲通道东西的时候一定要有东西,否则他会一直等待,就像上面的for循环,他就是一直等待导致了死锁,所以报错,不是因为他代码错误哈!
那么同时,如果你缓冲区有东西,就算进入了go协程也会很有可能被main程序抢占,`但如果缓冲区为空,那么main在取出数据的时候会因没有数据而等待某个协程给数据到通道然后取出来。`

对通道与协程之间的关系详细解释:(核心是能够让goroutine协程能够互相通信)

Go 提供了⼀种称为通道的机制,⽤于在 goroutine 之间共享数据。
Channels 通道可以共享内置、命名、结构和引⽤类型的值和 指针。
两种类型的通道:⽆缓冲通道和缓冲通道。
⽆缓冲通道⽤于执⾏goroutine之间的同步通信,⽽缓冲通道⽤于执⾏异步通信。
⽆缓冲通道保证在发送和接收发⽣的瞬间执⾏两个 goroutine 之间的交换。缓冲通道没有这样的保证。 协程之间⽆法直接通信,但可以使⽤通道来使协程之间互相通信
通道分为有缓冲和⽆缓冲,有缓冲,容量设置后,可以异步读取数据,不会堵塞,⽆缓冲的话, 放⼀个数据就必须取出来,否则就会阻塞

无缓冲通道

无缓冲通道的特性
发送:发送操作会阻塞,直到有 Goroutine 接收该值。
接收:接收操作会阻塞,直到有 Goroutine 发送一个值。举例子:
`假设你要在main中进行发送数据且读取数据是行不通的`,一定要开一个go协程发送数据,这样的话你的main就不会阻塞,因为go和main是互不干扰的,但是go协程可能执行权限轮不到他,main会抢占,所以这时候就能解释为什么要用sleep来休眠,等待go协程去执行发送通道了。
(当然sleep这种方式只是演示,实际中不会用,有其他方法解决)

有缓冲通道

在带缓冲的通道中,只要缓冲区未满,发送不会阻塞
`只要缓冲区未空,接收不会阻塞`

创建⽆缓冲和缓冲通道

make(chan int) //整型⽆缓冲通道,这里就是没有缓冲make(chan int ,1) //整型有缓冲通道,1也代表有一个缓冲//注意甄别:缓冲和通道是不一样的,通道是通道,你缓冲是拿来缓冲的,懂吧hhh?

示例代码:

package mainimport ("fmt""math/rand""time"
)var ch = make(chan int, 10)func Ion() {rand.Seed(time.Now().UnixNano())intn := rand.Intn(100)fmt.Println("随机数为:", intn)ch <- intnch <- 2  //对比数据ch <- 55  //对比数据
}
func main() {defer close(ch) //main关闭后,关闭通道//ch <- 2如果这里注释打开,就不会进入下面的go协程,因为主程序很快抢占回来执行结束,那么go的协程在go 的main运行完就无法运行了go Ion()fmt.Println("Wait...")data := <-chfmt.Println("值为:", data)  //最终取出来的是随机数fmt.Println("Done")
}

等协程

sync.WaitGroup同步

在上述中学到了,通道取出来数据是需要等待通道中有数据的,不然会阻塞,那么这当然是一种很好的同步方式,但是当我们go协程开启后,与main运行不相干的时候,main是不管协程的。main:“抢得过我再说”。

三个核心

var wg sync.WaitGroup
wg.Add(1)
wg.Done()  //这个函数里面其实是执行wg.Add(-1)
wg.Wait()

单单看上面三个函数其实就能理解了

当我们需要go协程与主函数同步执行的时候,可以用add添加,然后wait等待,那么怎么判断是否需要继续wait呢,那么就是用done来判断了,如果一直减到0的时候就不用wait了

示例代码:

//这样的话就不用sleep函数了,sleep我们也不清楚要多少秒才能让协程完成,所以肯定是需要这种同步机制来操作
func wq() {  var wg sync.WaitGroup//比如你每次进入go协程之前都add一下for i := 0; i < 10; i++ {fmt.Println("进入go协程之前add一下")wg.Add(1)go func(n int) {fmt.Println("我是协程:", n)defer wg.Done() //通知一下go协程OK了,然后defer是为了防止中途报错无法释放。defer闭包释放好一点。}(i)}//这里需要等待wait一下wg.Wait()fmt.Println("等待结束")
}

Runtime包

这个也可以让go协程同步

Gosched()

这个函数是用来等待协程的,如果还有协程在执行,main就会让出执行权利,实现同步

func runtime_go_Gosched() {fmt.Println("演示Gosched")go func() {fmt.Println("go协程执行中...")runtime.Goexit()fmt.Println("go协程还能执行吗...")}()runtime.Gosched() //等待go协程执行完fmt.Println("演示Gosched结束")}
Goexit()

exit顾名思义退出,当你go协程执行的时候可以用这个退出,然后回到外层函数继续执行

func runtime_go_Goexit() {fmt.Println("演示Goexit")go func() {fmt.Println("go协程执行中...")for i := 0; i < 10; i++ {if i > 7 {fmt.Println("go协程退出:", i)runtime.Goexit()}}}()runtime.Gosched() //等待go协程执行完fmt.Println("演示Goexit结束")
}

区别

sync.WaitGroup的作用一般是用于等待一组 goroutine 完成任务,done之后就不会阻塞了,即使你goroutine任务done完的代码后面还有代码任务,那么这时候goroutine就看情况抢占调度了,运气不好或者你main也没啥可执行的了就会直接结束,结束了你的go携程里面就算有代码可能抢不过main调度。runtime就是用于等待或结束一个goruntine的,可以用多个,每一个go携程中都能够

同步变量

sync.Mutex互斥锁

不管是在以前学的线程还是go中的协程,开了多线程后都会面临着几个程序抢占同一个变量的事情,可能会产生死锁问题。

两个函数,加锁和解锁

var Lock sync.Mutex
Lock.Lock()
Lock.Unlock()

简单的示例代码,使用加减法队同一个变量操作,你就会发现不会发生死锁问题
这里采用了x=10,然后循环加减法50次,你会发现基本不会发生一个加分或者减法一直执行的事情发生。

func Mutex_lock() {var (x    int = 10wg   sync.WaitGroupLock sync.Mutex)var Add = func() {defer wg.Done()Lock.Lock()x += 1fmt.Println("x++:", x)Lock.Unlock()}var Sub = func() {defer wg.Done()Lock.Lock()x -= 1fmt.Println("x--:", x)Lock.Unlock()}for i := 0; i < 50; i++ {  //循环50次wg.Add(1)go Add()wg.Add(1)go Sub()}wg.Wait()fmt.Println("结束", x)
}

atomic原子变量

除了Mutex互斥锁能够实现变量的同步操作外,go中还提供了原子变量

因为原子操作不能被中断,所以它需要足够简单,并且要求快速。因此,操作系统层面只对二进制位或整数的原子操作提供了支持。
`原子操作只支持有限的数据类型`,所以多数场景下,往往互斥锁更合适。

这里用同样的一个操作,加减法来实现一个变量的同步。

简单了解一下几个函数(以int类型为例子,其他类型一样的)

//CAS 是 Compare-And-Swap(⽐较并交换)的缩写
//作用是:⽐较内存中的某个值是否等于预期值,如果相等则将其更新为新值
atomic.CompareAndSwapInt32(&i, 100, 200)//Load(读)
//读取过程中相当于我们Metux中的加锁解锁操作,不会发生死锁、变量值不同步问题
atomic.LoadInt32(&i)//Store(写)
atomic.StoreInt32(&i, 200)//高版本的go可以直接使用:
atomic.Load(var_p)
atomic.Store(var_p)

示例代码:

func myAtomic() {//实现域Metux一样的操作var (x  int32 = 10wg sync.WaitGroup)var Add = func() {wg.Done()atomic.AddInt32(&x, 3)fmt.Println("Add 1")}var Sub = func() {wg.Done()atomic.AddInt32(&x, -2)fmt.Println("Sub 1")}for i := 0; i < 5; i++ {wg.Add(1)go Add()wg.Add(1)go Sub()}wg.Wait()fmt.Println("结束", x)
}

Select

是Go中的⼀个控制结构,类似于switch语句,select会监听case语句中channel的读写操作,当case可读写的时候,就执行case,进行读写操作以及case里面写的语句,当然select是随机选择的

default是当case当中都没有可读写的channel了,就会执行default中的语句

注意事项:

select中的case语句必须是⼀个channel操作
多个case都,select会随机选出⼀个执⾏
`没有可运⾏的case语句,且没有default语句,select将会阻塞,直到某个case通信可以运⾏`

需求:

创建不同类型的chan通道,写入数据后通过case对他进行分类别读取。

示例代码理解最快:

func select_case() {var (wg        sync.WaitGroupd_int     = make(chan int)d_string  = make(chan string)d_float64 = make(chan float64))go func() {wg.Add(1) //进入一个go协程+1d_int <- 123d_int <- 456d_string <- "string1"d_string <- "string2"d_float64 <- 123.456//养成好习惯,如果你不用通道了及时关闭,否则后面遍历处理数据的时候就会出错close(d_int)close(d_string)close(d_float64)}()wg.Wait() //等待for {select {case r := <-d_int:fmt.Println("int:", r)case r := <-d_string:fmt.Println("string:", r)case r := <-d_float64:fmt.Println("float64:", r)default:fmt.Println("default")}time.Sleep(1 * time.Second)}
}

Ticker定时器

创建定时器后,会向定时器的管道中按照指定的周期时间,定期写入事件,所以当你在周期到了的时候读取数据就不会阻塞,你读取完之后,还没到周期时间执行就会一直阻塞。

go的定时器记住三个函数

time.NewTicker(time.Second * 1) //创建一个周期性定时器
ticker.C   //ticker通道,Ticker对外仅暴露一个channel
ticker.Stop()  //停止ticker

示例代码

func myTicker() {ticker := time.NewTicker(1 * time.Second)count := 1for _ = range ticker.C {fmt.Println("执行了:", count)count++if count >= 10 {ticker.Stop()break}}
}

控制并发数量

核心机制

这里只是教你如何控制并发数量的例子,并不一定要按照我的方法来。

  1. sem <- data 的作用
    • sem 是一个带缓冲的通道,缓冲区大小为 workers,即 sem := make(chan 类型, workers)
    • 当通道的缓冲区已满时,发送操作(sem <-)会阻塞,等待通道有空间释放后再继续
    • 如果通道没有满,发送会立即成功。
  2. <-sem 的作用
    • defer func() { <-sem }() 会在协程完成任务后,从通道中取出一个值,释放空间。
    • 释放的空间让之前阻塞的发送操作可以继续执行,从而不会导致程序崩溃或死锁。
//这里是端口扫描的代码,提前搬上来了解一下控制并发数量
func start_WaitGroup_scan_port() {var (wg      sync.WaitGroupch      = make(chan int, 1024) // 增加缓冲区,减少阻塞count   intworkers = 100 // 控制并发数)var scanPort = func(hostname string, port int) {defer wg.Done()address := fmt.Sprintf("%s:%d", hostname, port)conn, err := net.DialTimeout("tcp", address, 2*time.Second)if err == nil {conn.Close()ch <- port}}// 控制并发数,很关键的理解sem := make(chan int, workers)for i := 0; i < 65536; i++ {wg.Add(1)sem <- 1go func(port int) {defer func() { <-sem }()scanPort("127.0.0.1", port)}(i)}go func() {wg.Wait()close(ch)}()for port := range ch {fmt.Printf("open: %d\n", port)count++}fmt.Printf("------------Open ports: %d-------------\n", count)fmt.Println("------------Scan——Done------------")
}

并发编程阶段练习

重要的细节

wg.Wait() 移到一个匿名协程中处理,并在 wg.Wait() 之后关闭通道,保证了以下两点:

  1. 通道关闭时,没有协程向通道写数据
    • 主协程从通道 rep 中读取数据,不负责关闭通道。
    • 匿名协程负责等待所有任务完成并关闭通道,确保通道关闭与写入互相隔离。
  2. 避免主协程被抢占的死锁风险
    • 如果你直接在主协程中调用 wg.Wait()close(rep),主协程在执行时可能与其他协程抢占资源,导致协程无法按时完成。
    • 匿名协程中执行 wg.Wait() 独立于主协程,不受读取数据或其他逻辑的影响。

可以看到下面中

go func() {wg.Wait()close(rep)
}()

这里原本我是直接放在主函数使用,开一个匿名协程去执行的,但是gpt解释了一下
就是:不能够在主函数中直接等待与关闭,而是需要在go协程中进行,因为直接在主函数wait后就close的话会导致go协程中还有正在执行的,这就很矛盾了,我们的wait命名是等待,但是不要忘记了他其实是Add函数去判断是否还需要等待,如果你提前Done了,主函数会立即抢占,那么这时候你的go协程其实还有很多代码没有执行完成就被抢占回去了,然后后面就是你要进行close,那么后续的go协程write相关操作就会报错了

func start_WaitGroup_scan_port() {var (wg    sync.WaitGroupcount int = 0rep       = make(chan bool))var WaitGroup_scan_port = func(hostname string, port int) {address := fmt.Sprintf("%s:%d", hostname, port)             //ip端口conn, err := net.DialTimeout("tcp", address, 2*time.Second) //2秒if err != nil {wg.Done()rep <- false} else {conn.Close()fmt.Printf("open: %d\n", port)wg.Done()rep <- true}}for i := 0; i <= 65535; i++ {wg.Add(1)go WaitGroup_scan_port("127.0.0.1", i)}go func() {wg.Wait()close(rep)}()for v := range rep {if v {count++}}fmt.Printf("------------Open ports: %d-------------\n", count)fmt.Println("------------Scan——Done------------")
}

端口扫描

1.使用WaitGroup进行并发,需要控制并发数量,可能会因为资源不够而导致结果不准确、不完整。

func start_WaitGroup_scan_port() {var (wg      sync.WaitGroupch      = make(chan int, 1024) // 增加缓冲区,减少阻塞count   intworkers = 100 // 控制并发数)var scanPort = func(hostname string, port int) {defer wg.Done()address := fmt.Sprintf("%s:%d", hostname, port)conn, err := net.DialTimeout("tcp", address, 2*time.Second)if err == nil {conn.Close()ch <- port}}// 控制并发数sem := make(chan int, workers)for i := 0; i < 65536; i++ {wg.Add(1)sem <- 1go func(port int) {defer func() { <-sem }()scanPort("127.0.0.1", port)}(i)}go func() {wg.Wait()close(ch)}()for port := range ch {fmt.Printf("open: %d\n", port)count++}fmt.Printf("------------Open ports: %d-------------\n", count)fmt.Println("------------Scan——Done------------")
}

2.使用通道,即单协程进行端口扫描

func start_Ch_scan_port() {var (ch        = make(chan int)count int = 0)var Ch_scan_port = func(hostname string) {defer close(ch)for i := 0; i < 65535; i++ {//扫描到开放端口放到ch通道中即可,主函数一直等待读取通道信息address := fmt.Sprintf("%s:%d", hostname, i)                //ip端口conn, err := net.DialTimeout("tcp", address, 2*time.Second) //2秒if err == nil {conn.Close()ch <- i}}runtime.Goexit()}go Ch_scan_port("127.0.0.1")runtime.Gosched() //等待go协程执行完for i := range ch {fmt.Printf("open: %d\n", i)count++}fmt.Printf("------------Open ports: %d-------------\n", count)fmt.Println("------------Scan——Done------------")
}

股票监控

这一题中能够领悟到挺多东西。

1.管道的读取数据,一般是不同协程之间进行读写操作,所以我们需要对读或者写分开协程操作
2.go协程之间需要不断对一个数据进行操作的话,一般要开一个通道然后通过对通道进行读写操作

最终效果

image-20241120003644836

示例代码:

package mainimport ("fmt""math/rand""sync/atomic""time"
)var price atomic.Value           //股票价格
var priceCh = make(chan float64) //股票通道,读写之间的一个通道
var stopCh = make(chan bool)     //开关监控
// 模拟股票涨跌(随机)
func randGP() float64 {return rand.Float64()*10 + 100 //控制在110之间
}// ticker 定时获取股票价格。模拟监控股票,且变更十次
func myTicker() {ticker := time.NewTicker(time.Second * 1)for {select {case <-ticker.C:priceCh <- randGP()case <-stopCh:fmt.Println("结束监控。")return}}
}
func update_g() {for i := range priceCh {price.Store(i)fmt.Printf("股票更新为:%.2f\n", i)}
}
func main() {go myTicker()go update_g()time.Sleep(5 * time.Second) // 监控5秒close(stopCh)               // 结束监控,close后stopCh会等于0,case能够读取//获取最终股票价格fmt.Printf("最终股票价格为:%.2f\n", price.Load())
}

相关文章:

Go红队开发—并发编程

文章目录 并发编程go协程chan通道无缓冲通道有缓冲通道创建⽆缓冲和缓冲通道 等协程sync.WaitGroup同步Runtime包Gosched()Goexit() 区别 同步变量sync.Mutex互斥锁atomic原子变量 SelectTicker定时器控制并发数量核心机制 并发编程阶段练习重要的细节端口扫描股票监控 并发编程…...

使用自动化运维工具 Ansible 集中化管理服务器

一、概述 Ansible 是一款为类 Unix 系统开发的自由开源的配置和自动化工具 官方网站:https://www.ansible.com/ Ansible 成立于 2013 年,总部设在北卡罗来纳州达勒姆,联合创始人 ad Ziouani 和高级副总裁 Todd Barr都是红帽的老员工。Ansible 旗下的开源软件 Ansible 十分…...

数据集笔记:新加坡 一些交通的时间序列统计量

1 机动车年度保有量 data.gov.sg 各类机动车年度保有量 数据范围&#xff1a;2005年1月 - 2020年12月 1.1 数据说明 非高峰时段车辆 包括周末车&#xff08;Weekend Cars&#xff09;和 修订版非高峰时段车辆&#xff08;Revised Off Peak Cars&#xff09;&#xff0c;该…...

企业jsapi_ticket,java举例

在企业微信开发中&#xff0c;使用 Java 获取 jsapi_ticket 并生成签名的步骤如下。以下是完整的 Java 示例代码。 1. 获取 jsapi_ticket 的流程 获取 access_token。 使用 access_token 获取 jsapi_ticket。 使用 jsapi_ticket 生成签名&#xff08;signature&#xff09;。…...

【FL0090】基于SSM和微信小程序的球馆预约系统

&#x1f9d1;‍&#x1f4bb;博主介绍&#x1f9d1;‍&#x1f4bb; 全网粉丝10W,CSDN全栈领域优质创作者&#xff0c;博客之星、掘金/知乎/b站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战&#xff0c;以及程序定制化开发…...

智能图像处理平台:图像处理配置类

这里我们先修改一下依赖&#xff0c;不用JavaCV&#xff0c;用openCV。 导入依赖&#xff1a; <!-- JavaCV 依赖&#xff0c;用于图像和视频处理 --> <!-- <dependency>--> <!-- <groupId>org.bytedeco</groupId>--> &l…...

《深度剖析:生成对抗网络中生成器与判别器的高效协作之道》

在人工智能的前沿领域&#xff0c;生成对抗网络&#xff08;GAN&#xff09;以其独特的对抗学习机制&#xff0c;为数据生成和处理带来了革命性的变革。生成器与判别器作为GAN的核心组件&#xff0c;它们之间的协作效率直接决定了GAN在图像生成、数据增强、风格迁移等众多应用中…...

【多模态大模型论文精读】MinMo语音交互大模型

写在前面:你需要一个更丝滑的语音助手 想象一下,你与一个语音助手对话,不再需要“嘿,Siri”或“小爱同学”这样的唤醒词,也不需要等待它一字一句地蹦出回复。你们可以像朋友一样,随时打断、插话,甚至同时说话。语音助手不仅能听懂你说了什么,还能理解你的语气、情感,…...

22-接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 方法一&#xff1a;双指针法 思路 使用两个指针 left 和 right 分别指向数组的两端&#xff0c;同时记录左边的最大高度 leftMax 和右边的最大高度 rig…...

如何长期保存数据(不包括云存储)最安全有效?

互联网各领域资料分享专区(不定期更新): Sheet 前言 这个问题需要考虑多个方面,比如存储介质的寿命、数据完整性、访问的便捷性,还有成本等因素。长期保存的话,存储介质的耐久性很重要。比如常见的硬盘、SSD、光盘、磁带等,各有优缺点。机械硬盘(HDD)的寿命一般在3-5年,…...

k8s拉取harbor镜像部署

在k8s中创建凭证 首先在节点docker登录harbor&#xff0c; 登录成功之后会在$HOME/.docker/ 生成一个config.json文件&#xff0c;这个就是登录凭证&#xff0c;后面docker pull就不需要再登录了。但是如果在k8s发布pod或者deploment时&#xff0c;这个凭证要在k8s中创建一个对…...

一周一个Unity小游戏2D反弹球游戏 - 球板的发球

前言 本文将实现当游戏开始时球在球板上,且不具备物理性,在Windows平台上通过点击屏幕来球发射,安卓平台上当手指触摸到屏幕上时进行发球,并此时开始具备物理性。 发球逻辑 首先在球板上创建一个球的发射点,新建一个空的游戏物体,并命名为BallPoint,并将其作为SpringBoa…...

C 语言共用体:深入理解与实践】

目录 一、引言 二、共用体的定义和基本语法 三、共用体的使用 3.1 声明共用体变量 3.2 给共用体成员赋值 3.3 共用体的内存布局 四、共用体的应用场景 4.1 节省内存空间 4.2 处理不同类型的数据 五、共用体使用的注意事项 六、总结 一、引言 在 C 语言中&#xff0c;共…...

Oracle性能调优(一):时间模型统计

Oracle性能调优(一):时间模型统计 时间模型统计视图时间模型统计指标时间模型统计视图 📖 DB Time的含义: DB Time表示前台会话在数据库调用中所花费的总时间,它是衡量数据库实例总负载的一个重要指标。DB Time是从实例启动时开始累计测量的,其计算方法是将所有前台会话…...

012 rocketmq事务消息

文章目录 事务消息概念介绍交互流程事务消息原理TransactionListener接⼝TransactionProducer.javaTransactionConsumer.java 事务消息 内置topic中的消息对消费者不可见 本地事务mq消息事务消息 消息队列 RocketMQ 版提供的分布式事务消息适⽤于所有对数据最终⼀致性有强需求…...

SpringBoot原理-02.自动配置-概述

一.自动配置 所谓自动配置&#xff0c;就是Spring容器启动后&#xff0c;一些配置类、bean对象就自动存入了IOC容器当中&#xff0c;而不需要我们手动声明&#xff0c;直接从IOC容器中引入即可。省去了繁琐的配置操作。 我们可以首先将spring项目启动起来&#xff0c;里面有一…...

知识图谱+智能问诊预诊系统vue+django+neo4j架构、带问诊历史

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; &#x1f90d;编号&#xff1a;D032 &#x1f90d;智能问答&#xff1a;智能问答自诊、预诊功能&#xff0c;同时可以保存问答历史 &…...

DeepSeek 助力 Vue3 开发:打造丝滑的悬浮按钮(Floating Action Button)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…...

Java数据结构_一篇文章了解常用排序_8.1

本文所有排序举例均默认为升序排列。 目录 1. 常见的排序算法 2. 常见排序算法的实现 2.1 插入排序 2.1.1 基本思想&#xff1a; 2.1.2 直接插入排序 2.1.3 希尔排序&#xff08;缩小增量排序&#xff09; 2.2 选择排序 2.2.1 基本思想&#xff1a; 2.2.2 直接选择排…...

(南京观海微电子)——倍压设计与应用

在电路设计过程中&#xff0c;当后级需要的电压比前级高出数倍而所需要的电流并不是很大时&#xff0c;就可以使用倍压整流电路。倍压整流&#xff1a;可以将较低的交流电压&#xff0c;用耐压较高的整流二极管和电容器&#xff0c;“整”出一个较高的直流电压。 01 倍压整流电…...

AtCoder Beginner Contest AT_abc395_d ABC395D Pigeon Swap 题解

前言 在谎言中迷茫&#xff0c;试图躲避瓶颈。 可惜细节太多&#xff0c;浪费五发罚时。 一个绿名用户&#xff0c;被出题人卡住。 八十六分钟多&#xff0c;才看见一抹绿。 本题解 LaTeX \LaTeX LATE​X 格式可能不太美观&#xff0c;以内容为主。 题目大意 有一群鸽子和它…...

网络安全-使用DeepSeek来获取sqlmap的攻击payload

文章目录 概述DeepSeek使用创建示例数据库创建API测试sqlmap部分日志参考 概述 今天来使用DeepSeek做安全测试&#xff0c;看看在有思路的情况下实现的快不快。 DeepSeek使用 我有一个思路&#xff0c;想要测试sqlmap工具如何dump数据库的&#xff1a; 连接mysql数据库&#…...

【含文档+PPT+源码】基于SpringBoot+Vue医药知识学习与分享平台的设计与实现

项目介绍 本课程演示的是一款 基于SpringBootVue医药知识学习与分享平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署…...

coze生成的工作流,发布后,利用cmd命令行执行。可以定时发日报,周报等。让他总结你飞书里面的表格。都可以

coze生成的工作流&#xff0c;发布后&#xff0c;利用cmd命令行执行。可以定时发日报&#xff0c;周报等。让他总结你飞书里面的表格。都可以。 很简单。 准备工作&#xff0c;先发布你的工作流&#xff0c;和发布应用。 然后&#xff0c;点击扣子API 。 申请一个&#xff0…...

iOS for...in 循环

0x00 循环遍历一 输出结果是什么&#xff1f; NSMutableArray *marr [1, 2, 3].mutableCopy; for (NSNumber *number in marr) {NSLog("%", number);marr [4, 5, 6].mutableCopy; } NSLog("%", marr);0x01 循环遍历二 输出结果是什么&#xff1f; NS…...

《操作系统 - 清华大学》 9 -1:进程调度:背景

进程调度&#xff1a;基础概念与调度策略详解 在进程调度这部分&#xff0c;我们会涉及以下内容&#xff1a;背景介绍、各种各样的调度算法&#xff08;包括通用操作系统的调度算法以及针对实时系统的算法&#xff09;&#xff0c;还会额外介绍一种调度算法。 一、CPU调度的背…...

NumPy的介绍

第四章&#xff1a;NumPy 基础 一 NumPy是什么 NumPy 数字集装箱 超级计算器。 NumPy 就像一台超级计算器&#xff0c;但它的计算对象不是简单的数字&#xff0c;而是成百上千个数字组成的「数字表格」。 假设你要统计全班 50 个同学的身高、体重、成绩&#xff0c;手动计算…...

【Python】基础语法三

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解Python的函数、列表和数组。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff…...

使用 Spring Boot 和 Keycloak 的 OAuth2 快速指南

1. 概述 本教程是关于使用 Spring Boot 和 Keycloak 通过 OAuth2 配置后端的。 我们将使用 Keycloak 作为 OpenID 提供程序。我们可以将其视为负责身份验证和用户数据&#xff08;角色、配置文件、联系信息等&#xff09;的用户服务。它是最完整的 OpenID Connect &#xff0…...

第三十六:6.6. 【$refs、$parent】

概述&#xff1a; $refs用于 &#xff1a;父→子。 $parent用于&#xff1a;子→父。 // 向外部提供数据 defineExpose({house}) 原理如下&#xff1a; 属性说明$refs值为对象&#xff0c;包含所有被ref属性标识的DOM元素或组件实例。$parent值为对象&#xff0c;当前组件…...