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

(undone) MIT6.824 Lecture 02 - RPC and Threads

知乎专栏:https://zhuanlan.zhihu.com/p/641105196

原视频:https://www.bilibili.com/video/BV16f4y1z7kn?spm_id_from=333.788.videopod.episodes&vd_source=7a1a0bc74158c6993c7355c5490fc600&p=2


看知乎专栏

一、Why we choose go?

  • 对于线程和RPC调用支持非常好
  • 有一个好的垃圾回收机制,线程不需要去手动释放分配的空间
  • 编译型语言,运行时开销不大

二、What is Thread and Why We need?

  • 线程,多线程程序有多个执行点,共享地址空间,能够访问相同的数据。
  • Why?:
    • I/O 并发性 —— 同时发起多个网络请求
    • 允许多核并行 —— 让不同的goroutine在不同的核上运行
    • 便捷性 —— 可以在后台定期执行一些事情,可以启动一个线程

三、Thread Challenges:

  • 竞态条件( Race Conditions )
    • 解决办法1:避免共享内存(go推荐使用信道传值,而非直接共享)
    • 解决办法2:使用锁,让操作变成原子的。(go可以启用竞争检测器,-race参数,检测到大部分的竞态条件)
  • 合作 (coordination)/协作:
    • 解决办法1:信道,可以用于协调和共享
    • 解决办法2:条件变量
  • 死锁问题:不同的线程互相等待导致死锁
    • 最简单的死锁就是当你只有一个线程,且你在往某个信道里写数据的时候,由于没有线程从该信道中读取数据,所以会导致主线程阻塞,导致死锁发生。

四、Go 通过什么来应对这些挑战?

  • go可以启用竞争检测器,使用-race参数,检测到大部分的竞态条件
  • 当多个线程去共享一个变量的时候,你就要注意是否有竞态条件发生了
  • 示例程序:(有竞态发生的投票程序)
    • 在 main() 函数中,程序使用 rand.Seed() 函数初始化随机数生成器,以确保每次运行程序时生成的随机数序列都不同。
    • 然后,程序使用 for 循环启动了 10 个 goroutine(Go 语言中的轻量级线程),每个 goroutine 都会调用 requestVote() 函数,并根据返回值更新计数器 count 和 finished
    • requestVote() 函数会休眠一段随机时间来模拟远程调用,然后随机返回一个布尔值,模拟投票的过程。
    • 最后,程序使用 for 循环和条件语句等待投票结果。如果收到了至少 5 个投票,程序输出 “received 5+ votes!”;否则,程序输出 “lost”。
package mainimport "time"
import "math/rand"func main() {rand.Seed(time.Now().UnixNano())count := 0finished := 0// 使用 for 循环启动了 10 个 goroutine(Go 语言中的轻量级线程),每个 goroutine 都会调用 requestVote() 函数,// 并根据返回值更新计数器 count 和 finishedfor i := 0; i < 10; i++ {go func() {vote := requestVote()if vote {count++}finished++}()}// 等待得到 5 个 count,或者 finished == 10 为止for count < 5 && finished != 10 {// wait}if count >= 5 {println("received 5+ votes!")} else {println("lost")}
}// 随机睡眠一段时间,然后随机返回 0/1
func requestVote() bool {time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)return rand.Int() % 2 == 0
}
1)通过 锁和条件变量(在需要共享内存时适合使用)
  • 注意上述代码是有竞态条件的,多个线程会共享访问count和finished变量,所以我们可以加锁
    • 下面代码就是在访问变量前获取一个同步锁来解决问题
var mu sync.Mutex
for i := 0; i < 10; i++ {go func() {vote := requestVote()mu.Lock()defer mu.Unlock()if vote {count++}finished++}()
}for {mu.Lock() // TODO: 感觉这里不用加锁if count >= 5 || finished == 10 {break}mu.Unlock()
}
  • 然而,我们又发现,在判断count和finished是否满足条件的地方,实际上是一个不停的for循环,即在等待过程中是一个不停的获取锁释放锁的过程,我们称之为自旋,这会浪费CPU资源。一种方法是可以在每次for循环结束前Sleep一段时间,另一种方法就是使用条件变量,如下:
    • cond有两个原语:Signal和Broadcast,一个对应单独通知等待者,一个对应广播通知等待者,此处我们只有一个等待者所以两者效果一样。
    • Wait原语:使线程陷入睡眠,并释放和该条件变量相关联的锁,等待被唤醒。当其被唤醒时,重新拿到和该条件变量相关联的锁。
    • 下面的代码定义了一个同步锁,一个条件变量,条件变量和锁绑定。每次goroutine执行完毕后,会使用条件变量广播等待者。在后续循环时候,主线程先获得锁,然后条件变量调用wait原语,主线程陷入睡眠并释放和该条件变量相关联的锁,等待被唤醒。
var mu sync.Mutex
cond := sync.NewCond(&mu)for i := 0; i < 10; i++ {go func() {vote := requestVote()mu.Lock()defer mu.Unlock()if vote {count++}finished++cond.Broadcast()}()
}
mu.Lock()
for count < 5 && finished != 10 {cond.Wait()
}
2)通过 信道 Channels(在不需要共享内存时适合使用)
  • 如果使用信道来书写这个代码的话,就意味着竞态条件不存在了,因为修改count和finished变量的只有主线程,goroutine只会向信道发送变量值,然后主线程通过信道接受值并且修改count和finished变量的值。
ch := make(chan bool)
for i := 0; i < 10; i++ {go func() {ch <- requestVote()}()
}
for count < 5 && finished < 10 {v := <-ch // 主线程会在读取通道处阻塞,直到 go线程 向通道发送数据if v {count += 1}finished += 1
}

五、爬虫程序示例:

  • 目标:
    • I/O 并发性
    • 正确性:对于单个页面仅爬取一次
    • 多核并发性能
1、顺序执行版本
// 定义了一个接口,要求实现 Fetch 方法,用于获取某个 URL 下的所有链接。
type Fetcher interface {// Fetch returns a slice of URLs found on the page.// urls: 该页面包含的所有 URL([]string)// err: 如果发生错误(如 URL 不存在),返回错误信息。Fetch(url string) (urls []string, err error)
}// fakeFetcher is Fetcher that returns canned results. (模拟抓取器)
// fakeFetcher 是一个 map,键是 URL(string),值是指向 fakeResult 的指针。
type fakeFetcher map[string]*fakeResult// body: 模拟网页的内容(字符串)。
// urls: 该页面包含的所有链接([]string)。
type fakeResult struct {body stringurls []string
}// 检查 fakeFetcher 是否包含给定的 url:
func (f fakeFetcher) Fetch(url string) ([]string, error) {// 如果存在,返回该 URL 对应的 fakeResult.urls(所有子链接)。if res, ok := f[url]; ok {fmt.Printf("found:   %s\\n", url)return res.urls, nil}// 如果不存在,返回错误 not found: <url>。fmt.Printf("missing: %s\\n", url)return nil, fmt.Errorf("not found: %s", url)
}// fetcher 实际上是一个被填充了内容的 fakeFetcher
// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{"<http://golang.org/>": &fakeResult{"The Go Programming Language",[]string{"<http://golang.org/pkg/>","<http://golang.org/cmd/>",},},"<http://golang.org/pkg/>": &fakeResult{"Packages",[]string{"<http://golang.org/>","<http://golang.org/cmd/>","<http://golang.org/pkg/fmt/>","<http://golang.org/pkg/os/>",},},"<http://golang.org/pkg/fmt/>": &fakeResult{"Package fmt",[]string{"<http://golang.org/>","<http://golang.org/pkg/>",},},"<http://golang.org/pkg/os/>": &fakeResult{"Package os",[]string{"<http://golang.org/>","<http://golang.org/pkg/>",},},
}// 单线程顺序执行爬虫
func Serial(url string, fetcher Fetcher, fetched map[string]bool) {// 如果这个 url 已经访问过,直接 returnif fetched[url] {return}// 更新 map,表示这个 url 已经访问过fetched[url] = true// 调用 Fetch 爬取 url 中的 urlsurls, err := fetcher.Fetch(url)// 如果出错,返回if err != nil {return}// 若不出错,则对 urls 递归调用调用 Serialfor _, u := range urls {Serial(u, fetcher, fetched)}return
}func main() {fmt.Printf("=== Serial===\\n") // 打印序列字符串// 第一个参数是要爬取的网址// 第二个参数是 模拟的网络内容物// 第三个参数是一个空 map (字典)Serial("<http://golang.org/>", fetcher, make(map[string]bool))
}
2、并发执行版本:(使用互斥锁)
  • 使用互斥锁来保护对fetched变量的并发访问保护
  • 除了使用互斥锁以外,和顺序版本差不多,比较核心的是使用了WaitGroup,
    • 在每个线程开始时,调用done.Add(1)
    • 每个线程结束时,调用done.Done()
    • 然后在主线程里调用done.wait(),等待所有的线程返回后主线程才会结束
type fetchState struct {mu      sync.Mutexfetched map[string]bool
}
func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {f.mu.Lock()already := f.fetched[url]f.fetched[url] = truef.mu.Unlock()if already {return}urls, err := fetcher.Fetch(url)if err != nil {return}var done sync.WaitGroup  //使用waitGroup跟踪你有多少活动的进程,何时可以中止for _, u := range urls {done.Add(1)go func(u string) {defer done.Done()ConcurrentMutex(u, fetcher, f)}(u)  // (u) 是立即调用匿名函数并传递参数 u 的语法。// 如果不加 (u) 直接使用循环变量 u,所有 goroutine 会共享同一个 u 的引用}done.Wait()return
}func main() {fmt.Printf("=== ConcurrentMutex ===\\n")ConcurrentMutex("<http://golang.org/>", fetcher, makeState())
}
3、并发执行版本(使用信道)
  • 该版本采用了类似mapreduce的框架,使用协调者和工作者的结构
  • ConcurrentChannel函数,首先创建一个接收字符串数组的信道,并将初始要爬取的字符串数组赋值给信道,并且调用协调者函数,协调者函数创建一个fetched映射,然后第一个循环代表不断的从信道里获取url数组,如果信道没有东西则会阻塞。
  • 内部的循环,对于每一个url都启动一个worker线程去进行爬取,worker线程爬取完后还会将新获得的需要爬取的网址塞入信道中,让主线程的coordinator获取,同时,coordinator中使用n来跟踪当前正在执行的worker的数量,如果所有worker都结束执行了,则程序返回。
//
// Concurrent crawler with channels
//func worker(url string, ch chan []string, fetcher Fetcher) {// 获取参数 url 下的所有 urls,并返回urls, err := fetcher.Fetch(url)if err != nil {ch <- []string{}} else {ch <- urls}
}func coordinator(ch chan []string, fetcher Fetcher) {n := 1// 创建一个fetched映射fetched := make(map[string]bool)// 第一个循环代表不断的从信道里获取url数组,如果信道没有东西则会阻塞。for urls := range ch {for _, u := range urls {// 只爬取已经被爬取过的 urlif fetched[u] == false {// 遇到没被爬取过的,标记为 true,表示已被爬取fetched[u] = true// 表示正在被爬取的 url 数量 + 1n += 1// 对于每一个url都启动一个worker线程去进行爬取go worker(u, ch, fetcher)}}// 表示正在被爬取的 url 数量 - 1n -= 1// 当没有正在被爬取的 url 时,退出循环if n == 0 {break}}
}func ConcurrentChannel(url string, fetcher Fetcher) {// 创建一个接收字符串数组的信道 channelch := make(chan []string)// 将要爬取的第一个字符串数组赋值给信道go func() {ch <- []string{url}}()// 调用协调者函数coordinator(ch, fetcher)
}func main() {// 打印日志fmt.Printf("=== ConcurrentChannel ===\\n")// 调用并发爬虫,第一个参数是要爬取的网页,第二个参数是模拟网页库ConcurrentChannel("<http://golang.org/>", fetcher)
}

六、RPC:

1、目标:

  • 调用者能够像调用本地栈内的过程函数一样,调用远程服务器上的函数。

2、如何工作?

  • 客户端有一个Stub,负责将调用的函数,参数等进行序列化,并传输给服务器上的Stub
  • 服务器上的Stub进行反序列化,然后调用服务器本地的函数,并通过反向的类似的过程返回给客户端的Stub,Stub解析后返回给调用者。

在这里插入图片描述

3、在Go中如何使用?(以Key/Value 存储为例)

  • 这段代码实现了一个简单的分布式键值存储系统,其中包括客户端和服务器端两部分。客户端可以通过 RPC 调用服务器端的方法来进行数据存储和查询。
  • 客户端侧:
    • 定义了 PutArgs 和 PutReply 两个类型,分别表示客户端调用 Put 方法的参数和返回值;GetArgs 和 GetReply 两个类型,分别表示客户端调用 Get 方法的参数和返回值。
    • 实现了 connect、get 和 put 三个函数。connect 函数用于连接服务器端的 RPC 服务;get 和 put 函数分别用于从服务器端获取数据和向服务器端存储数据。
  • 服务器侧:
    • 代码实现了一个 KV 类型,它包含一个互斥锁和一个 map 类型的数据成员,表示存储的键值对数据。KV 类型实现了 Get 和 Put 两个方法,分别用于获取和存储数据。这两个方法都使用互斥锁来保证并发访问时的数据安全。
    • 接着,代码实现了一个 server 函数,它用于创建一个 RPC 服务器并注册 KV 对象。server 函数使用 TCP 协议监听端口 1234,并在收到客户端请求时调用 rpcs.ServeConn 方法处理请求。
  • 最后,在 main 函数中,代码调用 server 函数启动服务器端,并向服务器端存储了一个键值对 “subject”-“6.824”。然后,代码调用 get 函数从服务器端获取键值对 “subject” 的值,并将其输出到控制台。
package main// 导入所需的相关库
import ("fmt""log""net""net/rpc""sync"
)//
// Common RPC request/reply definitions
//
// 以下是 common 的 客户端 request/reply 相关定义,这里使用 KV 键值对操作
// Put 操作参数有两个,key: string 和 value: string
type PutArgs struct {Key   stringValue string
}// Put 操作没有返回值
type PutReply struct {
}// Get 操作参数有一个,key: string
type GetArgs struct {Key string
}// Get 操作有一个返回值 value: string
type GetReply struct {Value string
}//
// Client
//
// connect 函数会连接 1234 端口号
func connect() *rpc.Client {client, err := rpc.Dial("tcp", ":1234")if err != nil {log.Fatal("dialing:", err)}return client
}func get(key string) string {// 与服务器建立连接client := connect()// 构造 request 参数:向服务器要求获取 key="subject" 的值args := GetArgs{"subject"}reply := GetReply{}// 向服务器调用 KV.Get 方法err := client.Call("KV.Get", &args, &reply)if err != nil {log.Fatal("error:", err)}// 关闭连接client.Close()return reply.Value
}func put(key string, val string) {// 与服务器建立连接client := connect()// 构造 request 参数,放入 key="subject", value="6.824"args := PutArgs{"subject", "6.824"}reply := PutReply{}// 向服务器调用 KV.Put 方法err := client.Call("KV.Put", &args, &reply)if err != nil {log.Fatal("error:", err)}// 关闭连接client.Close()
}//
// Server
//
// 
// 定义了一个 KV 数据类型,里面包含一个互斥锁,和一个 K/V map 数据结构
type KV struct {mu   sync.Mutexdata map[string]string
}func server() {// 初始化一个 KV 类型kv := new(KV)// 初始化 kv.data 数据结构kv.data = map[string]string{}// 注册一个 RPC 服务,里面装着 kv 数据rpcs := rpc.NewServer()rpcs.Register(kv)// 监听 1234 端口l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}// 开启一个新的 go 线程go func() {// 这个 go 线程会循环监听 1234 端口并为其提供服务,知道发生错误为止for {conn, err := l.Accept()if err == nil {go rpcs.ServeConn(conn)} else {// 如果发生错误,停止 server 线程break}}l.Close()}()
}// (kv *KV): 这是一个属于 KV 结构体的方法
// 该方法获取 KV 变量的锁,随后根据 GetArgs.Key 返回一个值
func (kv *KV) Get(args *GetArgs, reply *GetReply) error {kv.mu.Lock()defer kv.mu.Unlock()reply.Value = kv.data[args.Key]return nil
}// (kv *KV): 这是一个属于 KV 结构体的方法
// 该方法获取 KV 变量的锁,随后根据参数存入一个 (K,V) 对
func (kv *KV) Put(args *PutArgs, reply *PutReply) error {kv.mu.Lock()defer kv.mu.Unlock()kv.data[args.Key] = args.Valuereturn nil
}//
// main
//// 这段代码实现了一个简单的分布式键值存储系统,其中包括客户端和服务器端两部分。
// 客户端可以通过 RPC 调用服务器端的方法来进行数据存储和查询。// 在 main 函数中,代码调用 server 函数启动服务器端,并向服务器端存储了一个键值对 "subject"-"6.824"。
// 然后,代码调用 get 函数从服务器端获取键值对 "subject" 的值,并将其输出到控制台。
func main() {// 使用 go function 建立一个新线程,启动 serverserver()// 主线程作为 client,向 server 放入 key="subject", value="6.824"put("subject", "6.824")fmt.Printf("Put(subject, 6.824) done\\n")// 主线程作为 client,向 server 请求 key="subject" 的值fmt.Printf("get(subject) -> %s\\n", get("subject"))
}

4、RPC semantics under failures: (当 RPC 操作失败,约定的处理)

  • at-least-once:客户端重复尝试请求,直到有一次 RPC 操作成功
  • at-most-once:客户端最多请求1次,服务器执行0次或1次(Go的RPC系统是这种) (最多一次,如果算子处理事件失败,算子将不再尝试该事件。)
  • exactly-once:非常困难,开销较大,需要状态管理 (严格地,有且仅处理一次)

看原视频补充 (TODO: here)

在这里插入图片描述
Go 的线程运行在一个运行时环境里,所有线程共享一块内存,每个线程有自己的 PC, stack, registers 等 (这些是存放在内存中的)。
此外,go 语言支持线程的 start/go, exit, stop, resume 等操作。

在这里插入图片描述
为什么要在 go 中使用 threads ? 是为了表达并行执行,包括:
1.I/O 并行
2.多核并行
3.方便地表达并行

在这里插入图片描述
编写 go 线程可能会遇到的挑战:

  • Race conditions 数据竞争
    • 解决方案1:避免数据共享
    • 方案2:使用锁
    • 方案3:使用 go 提供的 race detector
  • Coordination 协调 (比如 一个线程等待另一个线程完成)
    • 通道或者条件变量
  • 可能的死锁情况

在这里插入图片描述
通常而言,为了解决上述挑战,通常采用两种方案:
1.通道
2.锁 + 条件变量

TODO: here

相关文章:

(undone) MIT6.824 Lecture 02 - RPC and Threads

知乎专栏&#xff1a;https://zhuanlan.zhihu.com/p/641105196 原视频&#xff1a;https://www.bilibili.com/video/BV16f4y1z7kn?spm_id_from333.788.videopod.episodes&vd_source7a1a0bc74158c6993c7355c5490fc600&p2 看知乎专栏 一、Why we choose go&#xff1f…...

红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules)

红宝书第二十一讲&#xff1a;详解JavaScript的模块化&#xff08;CommonJS与ES Modules&#xff09; 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、模块化的意义&#xff1a;分而治之 模块化解决代码依赖混…...

输入百分比校验(数字非负数保留2位不四舍五入)

场景用于输入百分比&#xff0c;限制只能输入非负数&#xff0c;保留2位小数&#xff0c;且不四舍五入 以下举例环境 vue2 element-ui 请自行根据实际场景使用 html部分 <el-inputv-model"item.percentage"placeholder"请输入"maxlength"5"…...

Python----机器学习(KNN:决策边界,决策边界计算,交叉验证步骤)

一、KNN算法简介 1.1、定义 KNN&#xff08;K-Nearest Neighbor&#xff09;算法是一种基于实例的学习方法&#xff0c;通过测量数据点之间的距离进行分类或回归分析。它是一种简单易懂的多分类技术&#xff0c;依赖于距离最近的邻居来推断数据点的类别或数值&#xff0c;为许…...

SpringBoot 3+ Lombok日志框架从logback改为Log4j2

r要将Spring Boot 3项目中的日志框架从Logback切换到Log4j2&#xff0c;并配置按日期滚动文件和控制台输出&#xff0c;请按照以下步骤操作&#xff1a; 步骤 1&#xff1a;排除Logback并添加Log4j2依赖 在pom.xml中修改依赖&#xff1a; <dependencies><!-- 排除默…...

实战篇Redis

黑马程序员的Redis的笔记&#xff08;后面补一下图片&#xff09; 【黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】https://www.bilibili.com/video/BV1cr4y1671t?p72&vd_source001f1c33a895eb5ed820b9a4…...

c++-函数增强

一、编译器对函数名的处理 1. C与C的差异 C编译器&#xff1a;保留原始函数名&#xff0c;无额外处理。例如&#xff1a; int add(int a, int b) { return a b; } 在汇编代码中仍为add。 C编译器&#xff1a;通过name mangling&#xff08;名称修饰&#xff09;生成唯一函数…...

BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测(Matlab)

BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测&#xff08;Matlab&#xff09; 目录 BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测&#xff08;Matlab&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多…...

css基础之浮动相关学习

一、浮动基本介绍 在最初&#xff0c;浮动是用来实现文字环绕图片效果的&#xff0c;现在浮动是主流的页面布局方式之一。 效果/代码 图片环绕 代码 div {width: 600px;height: 400px;background-color: skyblue;}img {width: 200px;float: right;margin-right: 0.5em;}<…...

告别分库分表,时序数据库 TDengine 解锁燃气监控新可能

达成效果&#xff1a; 从 MySQL 迁移至 TDengine 后&#xff0c;设备数据自动分片&#xff0c;运维更简单。 列式存储可减少 50% 的存储占用&#xff0c;单服务器即可支撑全量业务。 毫秒级漏气报警响应时间控制在 500ms 以内&#xff0c;提升应急管理效率。 新架构支持未来…...

1.3 斐波那契数列模型:LeetCode 746. 使用最小花费爬楼梯

动态规划解最小花费爬楼梯问题&#xff1a;LeetCode 746. 使用最小花费爬楼梯 1. 题目链接 LeetCode 746. 使用最小花费爬楼梯 题目要求&#xff1a;给定一个整数数组 cost&#xff0c;其中 cost[i] 是从楼梯第 i 阶向上爬所需支付的费用。你可以从下标 0 或 1 的台阶开始爬&a…...

8.4考研408简单选择排序与堆排序知识点深度解析

考研408「简单选择排序与堆排序」知识点全解析 一、简单选择排序 1.1 定义与核心思想 简单选择排序&#xff08;Selection Sort&#xff09;是一种选择排序算法&#xff0c;其核心思想是&#xff1a; 每趟选择&#xff1a;从待排序序列中选择最小&#xff08;或最大&#x…...

【个人笔记】用户注册登录思路及实现 springboot+mybatis+redis

基本思路 获取验证码接口 验证码操作用了com.pig4cloud.plugin的captcha-core这个库。 AccountControl的"/checkCode"接口代码&#xff0c;通过ArithmeticCaptcha生成一张验证码图片&#xff0c;通过text()函数得到验证码的答案保存到变量code&#xff0c;然后把图…...

LiteDB 数据存储与检索效率优化的最佳实践指导

一、引言 在当今数字化时代,数据处理和存储变得至关重要。对于小型项目或者嵌入式系统而言,需要一种轻量级、高效且易于使用的数据库解决方案。LiteDB 作为一款嵌入式的 NoSQL 数据库,因其零配置、易于集成等特点,受到了开发者的青睐。然而,若要充分发挥其性能优势,就需…...

WEB安全--RCE--RCE的绕过

一、回调函数的绕过&#xff08;PHP&#xff09; 1.1、回调函数 1.1.1、原理&#xff1a; 回调函数&#xff08;Callback Function&#xff09;指的是将函数名或匿名函数作为参数传递给另一个函数&#xff0c;从而在特定条件下调用该函数。 以一个常见的回调函数为例&#…...

uni-app:指引蒙层

组件说明 指引蒙层组件: 通过id标签,突出对应id中的模块; 可以自定义提示词。 点击任意位置关闭蒙层 效果展示和使用示例 切换id之后的效果: 代码实现 <template><view class="guide-mask" v-if="showMask" @click="hideMask"&g…...

什么是CMS?常用CMS有哪些?

一、内容管理系统&#xff08;Content Management System&#xff09;‌ ‌什么是CMS‌&#xff1a;位于 Web 前端&#xff08;服务器&#xff09;和后端办公系统之间的软件系统&#xff0c;用于内容创建、编辑、审批和发布。支持文本、图片、视频、数据库等各类数字内容的管理…...

【Es】基础入门:开启全文搜索的大门

文章目录 一、Elasticsearch 是什么​二、核心概念解读​索引&#xff08;Index&#xff09;​文档&#xff08;Document&#xff09;​映射&#xff08;Mapping&#xff09;​分片&#xff08;Shard&#xff09;​副本&#xff08;Replica&#xff09;​ 三、基本操作入门​安…...

74. Linux设备树详解

一、什么是设备树 1、uboot启动内核用到zImage&#xff0c;imx6ull-alientek-emmc.dtb。bootz 80800000 – 83000000. 80800000 —zImage 83000000—dtb 2、设备树&#xff1a;设备和树。 设备树(Device Tree)&#xff0c;将这个词分开就是“设备”和“树”&#xff0c;描述设…...

从责任链模式聊到aware接口

从责任链模式聊到aware接口 责任链是什么&#xff1f; 责任链模式是一种行为型设计模式&#xff0c;将多个对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;让多个对象都有机会处理这个请求&#xff0c;请求会顺着链传递&#xff0c;直到某个对象处理它为止。…...

在win11 环境下 新安装 WSL ubuntu + 换国内镜像源 + ssh + 桌面环境 + Pyhton 环境 + vim 设置插件安装

在win11 环境下 新安装 WSL ubuntu ssh gnome 桌面环境 Pyhton 环境 vim 设置插件安装 简单介绍详细流程换国内镜像源安装 ssh 桌面环境python 环境vim 设置插件安装 简单介绍 内容有点长&#xff0c;这里就先简单描述内容了。主要是快速在 Win11 搭建一个 wsl 的 linux 环…...

考研408-数据结构完整代码 线性表的链式存储结构 - 单链表

单链表操作详解&#xff08;C实现&#xff09; 目录 单链表尾插法创建单链表头插法创建删除指定节点按值查找按序号查找插入节点完整代码示例注意事项总结 尾插法创建 #include<bits/stdc.h> using namespace std;typedef struct LNode {int data;struct LNode* next;…...

使用Python爬虫获取淘宝App商品详情

在电商领域&#xff0c;获取商品详情数据对于市场分析、竞品研究和用户体验优化至关重要。淘宝作为国内领先的电商平台&#xff0c;提供了丰富的商品资源。虽然淘宝App的数据获取相对复杂&#xff0c;但通过Python爬虫技术&#xff0c;我们可以高效地获取淘宝App商品的详细信息…...

在 VMware Workstation 17 中安装的 Ubuntu 虚拟机无法使用桥接模式

在 VMware Workstation 17 中安装的 Ubuntu 虚拟机无法使用桥接模式时&#xff0c;通常是由于 网络配置错误、桥接适配器选择不当或主机网络环境限制 导致。以下是详细的排查和解决方法&#xff1a;我采用第一步就解决了问题 1. 检查 VMware 桥接模式配置 步骤 1&#xff1a;…...

2025前端八股文终极指南:从高频考点到降维打击的面试突围战

2025前端八股文终极指南&#xff1a;从高频考点到降维打击的面试突围战 一、2025前端八股文核心考点重构 1.1 新型响应式系统三连问 Vue3信号式响应性&#xff1a; // 信号式响应性底层实现 const [count, setCount] createSignal(0) effect(() > {console.log("当…...

MIPS-32架构(寄存器堆,指令系统,运算器)

文章目录 0 Preview:寄存器32通用0 $zero1 $at2—3 \$v0-$v14—7 \$a0-$a38—15 \$t0-$t716—23 \$s0-$s724—25 \$t8-$t926—27 \$k0-$k128 $gp29 $sp30 $fp 指令系统运算存储器 0 Preview: MIPS架构有32位版本和64位版本&#xff0c;本文介绍32位版本 寄存器 正如笔者曾说…...

MySQL数据库和表的操作之SQL语句

&#x1f3af; 本文专栏&#xff1a;MySQL深入浅出 &#x1f680; 作者主页&#xff1a;小度爱学习 MySQL数据库和表的操作 关系型数据库&#xff0c;都是遵循SQL语法进行数据查询和管理的。 SQL语句 什么是sql SQL&#xff1a;结构化查询语言(Structured Query Language)&…...

Ubuntu在VMware中无法全屏

Ubuntu在VMware中无法全屏 方法&#xff1a;安装open-vm-tools 在Ubuntu打开终端&#xff1a; 1.输入&#xff1a; sudo apt-get install open-vm-tools2.安装依赖&#xff1a; sudo apt-get install open-vm*3.重启Ubuntu reboot...

[C++面试] 智能指针面试点(重点)续3

[C面试] RAII资源获取即初始化&#xff08;重点&#xff09;-CSDN博客 [C面试] 智能指针面试点&#xff08;重点&#xff09;-CSDN博客 [C面试] 智能指针面试点&#xff08;重点&#xff09;续1-CSDN博客 [C面试] 智能指针面试点&#xff08;重点&#xff09;续2-CSDN博客 …...

借助FastAdmin和uniapp,高效搭建AI智能平台

在数字化办公时代&#xff0c;效率与协作是企业发展的核心竞争力。传统的办公工具虽然功能丰富&#xff0c;但在面对复杂多变的团队协作需求时&#xff0c;往往显得力不从心。为了解决这一痛点&#xff0c;我们推出了一款全新的办公AI平台&#xff0c;它不仅能够满足文字和语音…...

【弹性计算】异构计算云服务和 AI 加速器(四):FPGA 虚拟化技术

《异构计算云服务和 AI 加速器》系列&#xff0c;共包含以下文章&#xff1a; 异构计算云服务和 AI 加速器&#xff08;一&#xff09;&#xff1a;功能特点异构计算云服务和 AI 加速器&#xff08;二&#xff09;&#xff1a;适用场景异构计算云服务和 AI 加速器&#xff08;…...

Unity功能模块一对话系统(5)-完善对话流程及功能

现在我们的文本仍然是单句显示的&#xff0c;这还不是我们想要的效果&#xff0c;本期让我们完善对话流程&#xff0c;并使用SO&#xff08;ScriptableObject&#xff09;来进行一段对话序列的配置和动态加载。 最终效果功能展示 功能一&#xff1a;场景内可根据资源序号ID来…...

Ubuntu 系统中安装 Nginx

步骤 1&#xff1a;更新软件包列表 在安装前&#xff0c;先更新系统软件包列表&#xff1a; sudo apt update步骤 2&#xff1a;安装 Nginx 通过 apt 直接安装 Nginx&#xff1a; sudo apt install nginx -y步骤 3&#xff1a;验证安装 安装完成后&#xff0c;检查 Nginx …...

Elasticsearch:使用 Azure AI 文档智能解析 PDF 文本和表格数据

作者&#xff1a;来自 Elastic James Williams 了解如何使用 Azure AI 文档智能解析包含文本和表格数据的 PDF 文档。 Azure AI 文档智能是一个强大的工具&#xff0c;用于从 PDF 中提取结构化数据。它可以有效地提取文本和表格数据。提取的数据可以索引到 Elastic Cloud Serve…...

如何 编译 px4

DeepSeek-R1满血版 回答完成 深度思考完成 用时27秒&#xff0c;搜索全网29篇资料 以下是详细的PX4固件编译教程&#xff0c;综合多平台及常见问题解决方案&#xff1a; 一、环境准备 ‌系统要求‌ ‌Ubuntu 20.04/22.04‌&#xff08;推荐&#xff09;或 ‌MacOS 12‌ Windo…...

pycharm2024.1.1版本_jihuo

目录 前置&#xff1a; 步骤&#xff1a; step one 下载软件 step two 卸载旧版本 1 卸载软件 2 清除残余 step three 下载补丁 step four 安装2024.1.1版本软件 step five 安装补丁 1 找位置放补丁 2 自动设置环境变量 step six 输入jihuo码 前置&#xff1a; 之…...

LLaMA Factory微调后的大模型在vLLM框架中对齐对话模版

LLaMA Factory微调后的大模型Chat对话效果&#xff0c;与该模型使用vLLM推理架构中的对话效果&#xff0c;可能会出现不一致的情况。 下图是LLaMA Factory中的Chat的对话 下图是vLLM中的对话效果。 模型回答不稳定&#xff1a;有一半是对的&#xff0c;有一半是无关的。 1、未…...

Vulnhub:Digitalword.local: FALL靶机渗透

将靶机按照图中连接方式打开&#xff0c;fall在virtualBox中打开 信息收集 扫描得ip arp-scan -l 扫描端口 nmap -A -T4 -sV -p- 扫描目录 gobuster dir -u http://192.168.117.160 -x php,txt,html -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt 一个一个…...

androidstudio安装完成后创建新的示例项目编译报错解决

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 安装完成android studio想要编译一个自带的demo项目&#xff0c;没想到一直有编译报错&#xff0c;最后终于搞好了&#xff0c;记录下避免再踩坑。 androidstudio安装完成后创建新的示例项目编译报错…...

C#/.NET/.NET Core技术前沿周刊 | 第 31 期(2025年3.17-3.23)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…...

QT 跨平台发布指南

一、Windows 平台发布 1. 使用 windeployqt 工具 windeployqt --release --no-compiler-runtime your_app.exe 2. 需要包含的文件 应用程序 .exe 文件 Qt5Core.dll, Qt5Gui.dll, Qt5Widgets.dll 等 Qt 库 platforms/qwindows.dll 插件 styles/qwindowsvistastyle.dll (如果使…...

数制——FPGA

1、定点数 定点数的三种表示方式&#xff1a; 原码&#xff1a;符号位 绝对值 表示方法 反码&#xff1a;正数的反码表示 与原码表示一致&#xff0c;负数的反码表示 除符号位&#xff0c;其他位全都取反 补码&#xff1a;正数的补码表示 与原码表示一致&#xff0c;负数的补码…...

车载以太网网络测试 -25【SOME/IP-报文格式-1】

1 摘要 本专题接着上一专题对SOME/IP进行介绍&#xff0c;主要对SOME/IP报文格式以及定义的字段进行详细介绍&#xff0c;有助于在实际项目过程中对SOME/IP报文的理解。 上文回顾&#xff1a; 车载以太网网络测试 -24【SOME/IP概述】 2 SOME/IP-报文格式 通过上个专题介绍&a…...

Kubernetes》》k8s》》Replication Controller

RC(Replication Controller) 应用托管在kubernetes之后&#xff0c;kubernetes需要保证应用能够持续运行&#xff0c;这是RC的工作内容&#xff0c;它会确保任何时间kubernetes中都有指定数量的Pod在运行。在此基础上&#xff0c;RC还提供了一些更高级的特性&#xff0c;比如滚…...

力扣HOT100之矩阵:73. 矩阵置零

这道题我没有想到什么好的办法&#xff0c;直接暴力AC了&#xff0c;直接遍历两次矩阵&#xff0c;第一次遍历用两个向量分别记录出现0的行数和列数&#xff0c;第二次遍历就判断当前的元素的行数或者列数是否出现在之前的两个向量中&#xff0c;若出现了就直接置零&#xff0c…...

天锐蓝盾终端安全防护——企业终端设备安全管控

从办公室的台式电脑到员工手中的移动终端&#xff0c;这些设备不仅是工作的得力助手&#xff0c;更是企业数据的重要载体。然而&#xff0c;随着终端设备的广泛使用&#xff0c;安全风险也如影随形。硬件设备使用不当、数据随意传输等问题频发&#xff0c;使得企业数据面临着泄…...

【博客】使用GithubAction自动同步obisidian和hexo仓库

使用Github Action自动同步obisidian和hexo仓库&#xff0c;避免手动操作。 本文首发于❄慕雪的寒舍 1. 烦恼 先来说说慕雪现在的笔记和博客是怎么管理的吧&#xff0c;我正在使用两套笔记软件 思源笔记&#xff1a;私密性高一些&#xff0c;不是博客的笔记都在这里面。由于思…...

UE5学习笔记 FPS游戏制作23 区分敌我,寻找敌对角色

文章目录 方法1 tag方法2 变量添加变量和函数防止队友伤害 修改Task使用球形检测查找敌人场景面板直接编辑属性 方法1 tag 角色蓝图身上有一个tag标签&#xff0c;可以通过标签内容区分队伍 通过ActorHasTag判断一个Actor是否有对应的标签 方法2 变量 添加变量和函数 在s…...

ECharts各类炫酷图表/3D柱形图

一、前言 最近鸡米花实现了各类的炫酷的图表&#xff0c;有3D柱形图、双边柱形图以及异形柱形图&#xff0c;好了&#xff0c;直接上图&#xff1a; 二、效果图 一个个来吧&#xff0c;下面就是代码啦&#xff0c;注意&#xff0c;一下图表展示的宽高均为800px*300px 三、异形横…...

AI基础03-视频数据采集

上篇文章我们学习了图片的数据采集&#xff0c;今天主要了解一下视频数据采集的方法。视频是由一系列图像构成的&#xff0c;其中每一张图片就是一帧。视频数据采集方法通常有自动图像采集和基于处理器的图像采集两种。我们学习一下如何利用python 工具和笔记本计算机摄像头进行…...