GoLong的学习之路(二十三)进阶,语法之并发(go最重要的特点)(锁,sync包,原子操作)
这章是我并发系列中最后的一章。这章主要讲的是锁。但是也会讲上一章channl遗留下的一些没有讲到的内容。select关键字的用法,以及错误的一些channl用法。废话不多说。。。
文章目录
- select多路复用
- 通道错误示例
- 并发安全和锁
- 问题描述
- 互斥锁
- 读写互斥锁
- sync
- sync.WaitGroup
- 加载配置文件示例
- 并发安全的单例模式
- sync.Map
- 原子操作
- 读取操作
- 写入操作
- 修改操作
- 交换操作
- 比较并交换操作
select多路复用
使用场景
:需要同时从多个通道接收数据
通道在接收数据时,如果没有数据可以被接收那么当前 goroutine
将会发生阻塞。
当然办法不是没有。遍历呗。
for{// 尝试从ch1接收值data, ok := <-ch1// 尝试从ch2接收值data, ok := <-ch2…
}
这种方式虽然可以实现从多个通道接收值的需求,但是程序的运行性能会差很多。
Go 语言内置了select
关键字,使用它可以同时响应多个通道的操作。
Select
的使用方式类似于之前学到的 switch 语句
,它也有一系列 case 分支
和一个默认的分支
。
每个 case 分支
会对应一个通道的通信
(接收或发送)过程。select
会一直等待,直到其中的某个case
的通信操作完成时,就会执行该 case 分支
对应的语句
select {
case <-ch1://...
case data := <-ch2://...
case ch3 <- 10://...
default://默认操作
}
Select 语句具有以下特点:
- 可处理
一个或多个 channel
的发送/接收操作。 - 如果
多个 case 同时满足
,select
会随机
选择一个执行。 - 对于没有
case
的select
会一直阻塞,可用于阻塞 main 函数,防止退出。
package mainimport "fmt"func main() {ch := make(chan int, 1)for i := 1; i <= 10; i++ {select {case x := <-ch:fmt.Println(x)case ch <- i:}}
}
代码首先是创建了一个缓冲区
大小为1
的通道 ch
,进入 for 循环后:
- 第一次循环时
i = 1
,select 语句中包含两个 case 分支
,此时由于通道中没有值可以接收,所以x := <-ch
这个 case 分支不满足,而ch <- i
这个分支可以执行,会把1发送到通道中,结束本次 for 循环; - 第二次 for 循环时,
i = 2
,由于通道缓冲区已满,所以ch <- i
这个分支不满足,而x := <-ch
这个分支可以执行,从通道接收值1并赋值给变量 x ,所以会在终端打印出 1; - 后续的 for 循环以此类推会依次打印出3、5、7、9
简单而言就是,当i
为偶数的时候,执行的通道里输出。
通道错误示例
示例1
// demo1 通道误用导致的bug
func demo1() {wg := sync.WaitGroup{}ch := make(chan int, 10)for i := 0; i < 10; i++ {ch <- i}close(ch)wg.Add(3)for j := 0; j < 3; j++ {go func() {for {task := <-ch// 这里假设对接收的数据执行某些操作fmt.Println(task)}wg.Done()}()}wg.Wait()
}
匿名函数所在的 goroutine 并不会按照预期在通道被关闭后退出。
因为task := <- ch
的接收操作在通道被关闭后会一直接收到零值,而不会退出。此处的接收操作应该使用task, ok := <- ch
,通过判断布尔值ok
为假时退出;或者使用select
来处理通道。
修改后:
for j := 0; j < 3; j++ {go func() {for {task, ok := <-chfmt.Println(task)if !ok {break}}wait.Done()}()}wait.Wait()
其实不需要嵌套外循环的。不过为了方便观看就这样也了。
// demo2 通道误用导致的bug
func demo2() {ch := make(chan string)go func() {// 这里假设执行一些耗时的操作time.Sleep(3 * time.Second)ch <- "job result"}()select {case result := <-ch:fmt.Println(result)case <-time.After(time.Second): // 设置的超时时间return}
}
分析代码可以知道,此时有两个goroutine ,主方法走select,而另一个 goroutine 会走给通道输入值的操作。此时就有一个问题。从协程goroutine会等待三秒
,而主协程,指挥等待一秒
,然后按照超时操作弹出。
而这种问题的存在不是因为我们没有达到想要的结果,而是可能导致 goroutine 泄露(goroutine 并未按预期退出并销毁)
由于 select
命中了超时逻辑,导致通道没有消费者(无接收操作),而其定义的通道为无缓冲通道,因此 goroutine 中的ch <- "job result"
操作会一直阻塞,最终导致 goroutine
泄露。
上一章漏下的内容讲完
并发安全和锁
场景:
可能会存在多个 goroutine 同时操作一个资源(临界区)的情况,这种情况下就会发生竞态问题(数据竞态)
问题描述
package mainimport ("fmt""sync"
)var (x int64wg sync.WaitGroup // 等待组
)// add 对全局变量x执行5000次加1操作
func add() {for i := 0; i < 5000; i++ {x = x + 1}wg.Done()
}func main() {wg.Add(2)go add()go add()wg.Wait()fmt.Println(x)
}
每次执行都会生成不同的结果
原因:
我们开启了两个 goroutine 分别执行 add 函数,这两个 goroutine 在访问和修改全局的x变量时就会存在数据竞争,某个 goroutine 中对全局变量x的修改可能会覆盖掉另一个 goroutine 中的操作,所以导致最后的结果与预期不符
。
互斥锁
互斥锁
是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个 goroutine
可以访问共享资源。Go 语言中使用sync包中提供的Mutex
类型来实现互斥锁。
sync.Mutex提供了两个方法:
方法名 | 功能 |
---|---|
func (m *Mutex) Lock() | 获取互斥锁 |
func (m *Mutex) Unlock() | 释放互斥锁 |
通过锁修改
package mainimport ("fmt""sync"
)// sync.Mutexvar (x int64wg sync.WaitGroup // 等待组m sync.Mutex // 互斥锁
)// add 对全局变量x执行5000次加1操作
func add() {for i := 0; i < 5000; i++ {m.Lock() // 修改x前加锁x = x + 1m.Unlock() // 改完解锁}wg.Done()
}func main() {wg.Add(2)go add()go add()wg.Wait()fmt.Println(x)
}
此时就会达到我们的预期结果。
使用互斥锁能够保证同一时间有且只有一个 goroutine
进入临界区,其他的 goroutine 则在等待锁
;当互斥锁释放后,等待的 goroutine
才可以获取锁
进入临界区,多个 goroutine 同时等待一个锁时,唤醒的策略是随机的
。
读写互斥锁
互斥锁是完全互斥的,但是实际上有很多场景是读多写少的。
当我们并发的去读取一个资源而不涉及资源修改的时候是没有必要加互斥锁的,这种场景下使用读写锁是更好的一种选择。
读写锁在 Go 语言中使用sync包中的RWMutex类型
。
方法名 | 功能 |
---|---|
func (rw *RWMutex) Lock() | 获取写锁 |
func (rw *RWMutex) Unlock() | 释放写锁 |
func (rw *RWMutex) RLock() | 获取读锁 |
func (rw *RWMutex) RUnlock() | 释放读锁 |
func (rw *RWMutex) RLocker() Locker | 返回一个实现Locker接口的读写锁 |
读写锁分为两种:读锁
和写锁
- 当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待。
- 当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待。
package mainimport ("fmt""sync""time"
)var (x int64wg sync.WaitGroupmutex sync.MutexrwMutex sync.RWMutex
)// writeWithLock 使用互斥锁的写操作
func writeWithLock() {mutex.Lock() // 加互斥锁x = x + 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒mutex.Unlock() // 解互斥锁wg.Done()
}// readWithLock 使用互斥锁的读操作
func readWithLock() {mutex.Lock() // 加互斥锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒mutex.Unlock() // 释放互斥锁wg.Done()
}// writeWithLock 使用读写互斥锁的写操作
func writeWithRWLock() {rwMutex.Lock() // 加写锁x = x + 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒rwMutex.Unlock() // 释放写锁wg.Done()
}// readWithRWLock 使用读写互斥锁的读操作
func readWithRWLock() {rwMutex.RLock() // 加读锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒rwMutex.RUnlock() // 释放读锁wg.Done()
}func do(wf, rf func(), wc, rc int) {start := time.Now()// wc个并发写操作for i := 0; i < wc; i++ {wg.Add(1)go wf()}// rc个并发读操作for i := 0; i < rc; i++ {wg.Add(1)go rf()}wg.Wait()cost := time.Since(start)fmt.Printf("x:%v cost:%v\n", x, cost)}
func main() {// 使用互斥锁,10并发写,1000并发读do(writeWithLock, readWithLock, 10, 1000) // x:10 cost:1.466500951s// 使用读写互斥锁,10并发写,1000并发读do(writeWithRWLock, readWithRWLock, 10, 1000) // x:10 cost:117.207592ms
}
从最终的执行结果可以看出,使用读写互斥锁在读多写少的场景下能够极大地提高程序的性能。
不过需要注意的是如果一个程序中的读操作和写操作数量级
差别不大,那么读写互斥锁的优势就发挥不出来
。
有一点要注意在在这个实验中要明确一个已经知道的属性,那就是读操作,一定比写操作快。
在并发的时候说了,GO本体的标准库有个一个专门为并发实现的包sync
。
sync
sync.WaitGroup
在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup
来实现并发任务
的同步
。在这里插入代码片
方法名 | 功能 |
---|---|
func (wg * WaitGroup) Add(delta int) | 计数器 +delta |
func (wg *WaitGroup) Done() | 计数器 -1 (这个要搭配defer使用) |
func (wg *WaitGroup) Wait() | 阻塞直到计数器变为 0 |
sync.WaitGroup
内部维护着一个计数器,计数器的值可以增加
和减少
。
当我们启动了 N 个并发任务
时,就将计数器值增加N
。每个任务完成时通过调用 Done 方法将计数器减1
。通过调用 Wait
来等待并发任务执行完
,当计数器值为 0 时,表示所有并发任务已经完成。几乎同时(人类看来)输出结果
需要注意:
sync.WaitGroup是一个结构体,进行参数传递的时候要传递指针。## sync.Once
var wg sync.WaitGroupfunc hello() {defer wg.Done()fmt.Println("Hello Goroutine!")
}
func main() {wg.Add(1)go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")wg.Wait()
}
在某些场景下我们需要确保某些操作即使在高并发的场景下也只会被执行一次,例如只加载一次配置文件
等。
Go语言中的sync包中提供了一个针对只执行一次场景的解决方案——sync.Once
,sync.Once
只有一个Do方法
func (o *Once) Do(f func())
注意:
如果要执行的函数f需要传递参数就需要搭配闭包
来使用。
加载配置文件示例
延迟一个开销很大的初始化操作到真正用到它的时候再执行是一个很好的实践。
因为预先初始化一个变量(比如在init函数
中完成初始化)会增加程序的启动耗时,而且有可能实际执行过程中这个变量没有用上,那么这个初始化操作
就不是必须
要做的。
var icons map[string]image.Imagefunc loadIcons() {icons = map[string]image.Image{"left": loadIcon("left.png"),"up": loadIcon("up.png"),"right": loadIcon("right.png"),"down": loadIcon("down.png"),}
}func loadIcon(s string) image.Image {return nil
}// Icon 被多个goroutine调用时不是并发安全的
func Icon(name string) image.Image {if icons == nil {loadIcons()}return icons[name]
}
多个 goroutine
并发调用Icon
函数时不是并发安全的,现代的编译器和CPU可能会在保证每个 goroutine
都满足串行一致的基础上自由地重排访问内存的顺序。(指令重排序)
loadIcons函数可能会被重排为以下结果:
func loadIcons() {icons = make(map[string]image.Image)icons["left"] = loadIcon("left.png")icons["up"] = loadIcon("up.png")icons["right"] = loadIcon("right.png")icons["down"] = loadIcon("down.png")
}
千万别看这个顺序和前面定义的一样。那是每个goroutine的出来的结果。并不一个得出的。
在这种情况下就会出现即使判断了icons
不是nil
也不意味着变量初始化完成了。考虑到这种情况,我们能想到的办法就是添加互斥锁
,保证初始化icons
的时候不会被其他的 goroutine
操作,但是这样做又会引发性能问题。
所以此时就考虑:sync.Once
import ("image""sync"
)var icons map[string]image.Imagevar loadIconsOnce sync.Oncefunc loadIcons() {icons = map[string]image.Image{"left": loadIcon("left.png"),"up": loadIcon("up.png"),"right": loadIcon("right.png"),"down": loadIcon("down.png"),}
}func loadIcon(s string) image.Image {return nil
}// Icon 是并发安全的
func Icon(name string) image.Image {loadIconsOnce.Do(loadIcons)return icons[name]
}
func main() {}
并发安全的单例模式
package singletonimport ("sync"
)type singleton struct {}var instance *singleton
var once sync.Oncefunc GetInstance() *singleton {once.Do(func() {instance = &singleton{}})return instance
}
sync.Once
其实内部包含一个互斥锁
和一个布尔值
,互斥锁保证布尔值和数据的安全,而布尔值用来记录初始化是否完成
。
这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次
。
sync.Map
sync.Map
Go 语言中内置的 map 不是并发安全的.
错误例子
package mainimport ("fmt""strconv""sync"
)var m = make(map[string]int)func get(key string) int {return m[key]
}func set(key string, value int) {m[key] = value
}func main() {wg := sync.WaitGroup{}for i := 0; i < 10; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)set(key, n)fmt.Printf("k=:%v,v:=%v\n", key, get(key))wg.Done()}(i)}wg.Wait()
}
将上面的代码编译后执行,会报出fatal error: concurrent map writes
错误。我们不能在多个 goroutine 中并发对内置的 map 进行读写操作,否则会存在数据竞争问题。其实大家,自己运行一下就知道了。其实不一定会出现这个错误,但是有概率出现。所以能加锁就枷锁。
这种场景下就需要为map
加锁来保证并发的安全性了,Go语言的sync
包中提供了一个开箱即用的并发安全版 map——sync.Map
开箱即用表示其不用像内置的 map 一样使用 make 函数初始化就能直接使用。
方法名 | 功能 |
---|---|
func (m *Map) Store(key, value interface{}) | 存储key-value数据 |
func (m *Map) Load(key interface{}) (value interface{}, ok bool) | 查询key对应的value |
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) | 查询或存储key对应的value |
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) | 查询并删除key |
func (m *Map) Delete(key interface{}) | 删除key |
func (m *Map) Range(f func(key, value interface{}) bool) | 对map中的每个key-value依次调用f |
package mainimport ("fmt""strconv""sync"
)// 并发安全的map
var m = sync.Map{}func main() {wg := sync.WaitGroup{}// 对m执行20个并发的读写操作for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)m.Store(key, n) // 存储key-valuevalue, _ := m.Load(key) // 根据key取值fmt.Printf("k=:%v,v:=%v\n", key, value)wg.Done()}(i)}wg.Wait()
}
此时就安全了
说到枷锁操作,就不得不说一个东西。原子性操作
原子操作
针对整数数据类型(int32、uint32、int64、uint64)
我们还可以使用原子操作来保证并发安全,通常直接使用原子操作比使用锁操作效率更高。
Go语言中原子操作由内置的标准库sync/atomic
提供。(具体需要的话可以去看相关文档)
读取操作
func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
写入操作
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
修改操作
func AddInt32(addr *int32, delta int32) (new int32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
交换操作
func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
比较并交换操作
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
atomic
包提供了底层的原子级内存操作
,对于同步算法的实现
很有用。这些函数必须谨慎地保证正确使用
。
除了某些特殊的底层应用,使用通道
或者 sync 包的函数/类型
实现同步更好。
例子
package mainimport ("fmt""sync""sync/atomic""time"
)type Counter interface {Inc()Load() int64
}// 普通版
type CommonCounter struct {counter int64
}func (c CommonCounter) Inc() {c.counter++
}func (c CommonCounter) Load() int64 {return c.counter
}// 互斥锁版
type MutexCounter struct {counter int64lock sync.Mutex
}func (m *MutexCounter) Inc() {m.lock.Lock()defer m.lock.Unlock()m.counter++
}func (m *MutexCounter) Load() int64 {m.lock.Lock()defer m.lock.Unlock()return m.counter
}// 原子操作版
type AtomicCounter struct {counter int64
}func (a *AtomicCounter) Inc() {atomic.AddInt64(&a.counter, 1)
}func (a *AtomicCounter) Load() int64 {return atomic.LoadInt64(&a.counter)
}func test(c Counter) {var wg sync.WaitGroupstart := time.Now()for i := 0; i < 1000; i++ {wg.Add(1)go func() {c.Inc()wg.Done()}()}wg.Wait()end := time.Now()fmt.Println(c.Load(), end.Sub(start))
}func main() {c1 := CommonCounter{} // 非并发安全test(c1)c2 := MutexCounter{} // 使用互斥锁实现并发安全test(&c2)c3 := AtomicCounter{} // 并发安全且比互斥锁效率更高test(&c3)
}
相关文章:

GoLong的学习之路(二十三)进阶,语法之并发(go最重要的特点)(锁,sync包,原子操作)
这章是我并发系列中最后的一章。这章主要讲的是锁。但是也会讲上一章channl遗留下的一些没有讲到的内容。select关键字的用法,以及错误的一些channl用法。废话不多说。。。 文章目录 select多路复用通道错误示例并发安全和锁问题描述互斥锁读写互斥锁 syncsync.Wait…...
asp.net core 生命周期
在ASP.NET Core中,有三个重要的生命周期阶段: 请求生命周期(Request Lifecycle):请求生命周期从接收到客户端的HTTP请求开始,到响应结果发送给客户端结束。在请求生命周期中,ASP.NET Core会创建…...

Leetcode刷题详解—— 目标和
1. 题目链接:494. 目标和 2. 题目描述: 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - ,然后串联起所有整数,可以构造一个 表达式 : 例如,nums [2, 1] ,可…...
学习c#的第六天
目录 C# 判断 if 语句 嵌套 if 语句 switch 语句 嵌套 switch 语句 ? : 运算符 C# 循环 循环类型 while 循环 for/foreach 循环 do...while 循环 嵌套循环 循环控制语句 break 语句 continue 语句 无限循环 C# 判断 if 语句 在C#中,if语句用于根…...

第七章 :Spring Boot web开发常用注解(二)
第七章 :Spring Boot web开发常用注解(二) 前言 本章节知识重点:作者结合自身开发经验,以及觉察到的一个现象:Springboot注解全面理解和掌握的并不多,对注解进行了全面总结,共分两个章节,可以作为web开发工程师注解参考手册,SpringBoot常用注解大全,一目了然!。本…...

IOC - Google Guice
Google Guice是一个轻量级的依赖注入框架,专注于依赖注入和IoC,适用于中小型应用。 Spring Framework是一个全面的企业级框架,提供了广泛的功能,适用于大型企业应用。 是吧!IOC 容器不止Spring,还有Google Guice,来体…...
国际阿里云:Linux实例负载高问题排查和异常处理!!!
问题描述 在您使用ECS实例过程中,可能会遇到实例系统负载较高的情况,负载过高,可能会引发一系列异常问题,简单说您如下: CPU使用率或负载过高:一般来说,当CPU使用率≥80%时,定义为C…...

【数据结构】二叉树的遍历递归算法详解
二叉树的遍历 💫二叉树的结点结构定义💫创建一个二叉树结点💫在主函数中手动创建一颗二叉树💫二叉树的前序遍历💫调用栈递归——实现前序遍历💫递归实现中序和后序遍历 💫二叉树的结点结构定义 …...

百度王颖:百度文库以AI创作能力突破语言边界,促进思想碰撞和文化融通
1月9日,2023年世界互联网大会乌镇峰会“网络传播与文明交流互鉴论坛”召开。百度副总裁、互娱和垂类平台负责人王颖出席并发表“以技术搭建跨文化交流桥梁”主题演讲。她表示,在大模型的加持下,百度各个产品都在重构,通过技术助力…...

人工智能基础_机器学习023_理解套索回归_认识L1正则---人工智能工作笔记0063
然后上一节我们说了L1,L2正则是为了提高,模型的泛化能力, 提高泛化能力,实际上就是把模型的公式的w,权重值,变小对吧. 然后我们这里首先看第一个L1正则,是怎么做到把w权重变小的 可以看到最上面是线性回归的损失函数,然后 L1可以看到,这个正则,就是在损失函数的基础上给损失…...
Learning an Animatable Detailed 3D Face Model from In-The-Wild Images论文笔记
Learning an Animatable Detailed 3D Face Model from In-The-Wild Images论文笔记 论文目标:提出一个端到端的框架,可以从非受控的图片中学习高质量、可动画的3D人脸模型。论文方法:论文结果:论文意义: 论文目标:提出一个端到端的框架,可以从非受控的图片中学习高质量、可动画…...

Lenovo联想小新Air-14笔记本2021款AMD锐龙ALC版(82LM)原装出厂Win10镜像和Windows11预装OEM系统
下载链接:https://pan.baidu.com/s/1akLkXM2HIg3eO76jqM-LVA?pwdxvo6 提取码:xvo6 系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具:16G或以上的U盘 文件格式:…...

在程序中链接静态库
现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。 add_library(库名称 STATIC 源文件1 [源文件2] ...) link_libraries(<static lib> [<static lib>...]) 参数1:指定出要链接的静态库的名字 可以是全…...

TortoiseSVN 状态图标不显示的两种解决办法
文章目录 TortoiseSVN 方式解决注册表方式解决 TortoiseSVN 方式解决 在桌面或者资源管理器中鼠标右键打开 TortoiseSVN 设置选择 Icon Overlays (图标覆盖)Status cache(状态缓存) 选择 ‘Shell’ 选择 Icon Overlays(图标覆盖)…...

NSSCTF-Crypto入门题 练习记录贴 ‘‘一‘‘
文章目录 前言001[鹤城杯 2021]easy_crypto002[强网拟态 2021]拟态签到题003[SWPUCTF 2021 新生赛]crypto8004[SWPUCTF 2021 新生赛]crypto7005[SWPUCTF 2021 新生赛]crypto6006[SWPUCTF 2021 新生赛]ez_caesar007[SWPUCTF 2021 新生赛]crypto10008[鹤城杯 2021]A_CRYPTO009[SW…...

Day03:注意事项、this关键字、构造器、JavaBean、String、ArrayList
OOP的注意事项 类名要跟class文件名一致(一个class可以有多个类,但只有一个public,且与文件名一致),命名介意大驼峰;如果某个对象没有变量指向他,就成垃圾对象了(空指针)…...
【从0到1设计一个网关】性能优化---缓存
文章目录 为什么要用缓存?Caffeine Cache使用Caffeine效果演示为什么要用缓存? 首先先了解一下为什么在网关中我们需要用到缓存。 我们可以从如下几点来入手这个问题: 处理大规模流量: 网关是系统的入口,需要处理大规模的请求流量。高性能的网关能够快速而有效地处理大量…...

Typescript -尚硅谷
基础 1.ts是以js为基础构建的语言,是一个js的超集(对js进行了扩展); 2.ts(type)最主要的功能是在js的基础上引入了类型的概念; Js的类型是只针对于值而言,ts的类型是针对于变量而言 Ts可以被编译成任意版本的js,从而进一步解决了…...

以 Kubernetes 原生方式实现多集群告警
作者:向军涛、雷万钧 来源:2023 上海 KubeCon 分享 可观测性来源 在 Kubernetes 集群上,各个维度的可观测性数据,可以让我们及时了解集群上应用的状态,以及集群本身的状态。 Metrics 指标:监控对象状态的量…...

2023年A股借壳上市研究报告
第一章 借壳上市概况 1.1 定义 借壳上市作为一种独特的资本市场操作手法,历来是企业拓展融资渠道和实现市场战略目标的重要途径。具体来说,借壳上市可分为狭义与广义两种模式。在狭义的定义下,借壳上市是指一家已上市的公司的控股母公司&am…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...