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

news2025/4/26 9:26:47

知乎专栏: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 main

import "time"
import "math/rand"

func main() {
	rand.Seed(time.Now().UnixNano())

	count := 0
	finished := 0

	// 使用 for 循环启动了 10 个 goroutine(Go 语言中的轻量级线程),每个 goroutine 都会调用 requestVote() 函数,
	// 并根据返回值更新计数器 count 和 finished
	for 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 string
	urls []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 已经访问过,直接 return
	if fetched[url] {
		return
	}
	// 更新 map,表示这个 url 已经访问过
	fetched[url] = true
	// 调用 Fetch 爬取 url 中的 urls
	urls, err := fetcher.Fetch(url)
	// 如果出错,返回
	if err != nil {
		return
	}
	// 若不出错,则对 urls 递归调用调用 Serial
	for _, 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.Mutex
	fetched map[string]bool
}
func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
	f.mu.Lock()
	already := f.fetched[url]
	f.fetched[url] = true
	f.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 {
			// 只爬取已经被爬取过的 url
			if fetched[u] == false {
				// 遇到没被爬取过的,标记为 true,表示已被爬取
				fetched[u] = true
				// 表示正在被爬取的 url 数量 + 1
				n += 1
				// 对于每一个url都启动一个worker线程去进行爬取
				go worker(u, ch, fetcher)
			}
		}
		// 表示正在被爬取的 url 数量 - 1
		n -= 1
		// 当没有正在被爬取的 url 时,退出循环
		if n == 0 {
			break
		}
	}
}

func ConcurrentChannel(url string, fetcher Fetcher) {
	// 创建一个接收字符串数组的信道 channel
	ch := 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   string
	Value 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.Mutex
	data 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.Value

	return nil
}

//
// main
//

// 这段代码实现了一个简单的分布式键值存储系统,其中包括客户端和服务器端两部分。
// 客户端可以通过 RPC 调用服务器端的方法来进行数据存储和查询。

// 在 main 函数中,代码调用 server 函数启动服务器端,并向服务器端存储了一个键值对 "subject"-"6.824"。
// 然后,代码调用 get 函数从服务器端获取键值对 "subject" 的值,并将其输出到控制台。
func main() {
	// 使用 go function 建立一个新线程,启动 server
	server()
	// 主线程作为 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:非常困难,开销较大,需要状态管理 (严格地,有且仅处理一次)

看原视频补充 (不必,已经掌握足够多知识,没必要掌握所有细节)

在这里插入图片描述
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.锁 + 条件变量

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

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

相关文章

LayaAir3.3.0-beta.3重磅更新!Spine4.2、2D物理、UI系统、TileMap等全面升级!

正式版推出前&#xff0c;说明3.3的功能还没开发完。所以&#xff0c;又一大波更新来了~ 下面对重点更新进行说明。 Spine的重要更新 3.3.0-beta.3版本开始&#xff0c;新增了Spine 4.2 的运行时库&#xff0c;Spine动画上可以支持物理特性了。例如&#xff0c;下图右侧女孩在启…

【AI学习】机器学习算法

1&#xff0c;线性回归模型&#xff08;Linear Regression&#xff09;:预测连续数值 寻找自变量&#xff08;解释变量&#xff09;与因变量&#xff08;被解释变量&#xff09;之间的线性关联关系&#xff0c;通过构建线性方程来对数据进行拟合和预测。即两个变量之间是一次函…

【渗透测试】Vulnhub靶机-FSoft Challenges VM: 1-详细通关教程

下载地址&#xff1a;https://www.vulnhub.com/entry/fsoft-challenges-vm-1,402/ 目录 前言 信息收集 目录扫描 wpscan扫描 修改密码 反弹shell 提权 思路总结 前言 开始前注意靶机简介&#xff0c;当第一次开机时会报apache错误&#xff0c;所以要等一分钟后重启才…

【区块链+ 房产建筑】山东省建筑产业互联网平台 | FISCO BCOS 应用案例

山东省建筑产业互联网平台&#xff08;山东省弘商易盟平台&#xff09;是基于区块链技术构建的分布式产业互联网平台&#xff0c; 旨在把各企业内部的供应链协同管理系统&#xff08;包括采购或者SRM 系统&#xff0c; 以及销售或CRM 系统&#xff09;利用区块链技术链接起来&a…

国家天文台携手阿里云,发布国际首个太阳大模型“金乌”

2025年4月1日&#xff0c;中国科学院国家天文台与阿里云共同宣布推出全球首个太阳物理大模型“金乌”&#xff0c;在太阳活动预测领域实现颠覆性突破——其针对破坏性最强的M5级太阳耀斑预报准确率高达91%&#xff0c;远超传统数值模型&#xff0c;标志着人类对太阳的认知迈入“…

数据结构(5)——栈

目录 前言 一、栈的概念及其结构 二、栈的实现 2.1说明 2.2动态栈结构体定义 2.3初始化 2.4销毁 2.5进&#xff08;压&#xff09;栈 2.6检验栈是否为空 2.7弹&#xff08;出&#xff09;栈 2.8栈的元素个数 2.9访问栈顶元素 三、运行 总结 前言 栈是一种常见的…

Css径向渐变 - radial-gradient

由background-image: radial-gradient(at 75% 7%, blue 0px, transparent 50%);引出&#xff1a; 一、径向渐变是什么 径向渐变是颜色从一个中心点向外扩散的变化过程。 二、radial-gradient 函数是什么 1、使用语法&#xff1a; background-image: radial-gradient(shape si…

理解激活函数,多个网络层之间如何连接

1. 激活函数如何在两个层之间作用 如果不在两个层之间添加激活函数&#xff0c;模型将无法学习非线性关系&#xff0c;表现出像线性模型一样的局限性。 LeakyReLU(0.2) 是一个激活函数&#xff0c;它的作用是对每一层的输出进行非线性转换。激活函数通常在神经网络中用于增加网…

HTML5 Canvas绘画板项目实战:打造一个功能丰富的在线画板

HTML5 Canvas绘画板项目实战&#xff1a;打造一个功能丰富的在线画板 这里写目录标题 HTML5 Canvas绘画板项目实战&#xff1a;打造一个功能丰富的在线画板项目介绍技术栈核心功能实现1. 画板初始化与工具管理2. 多样化绘画工具3. 事件处理机制 技术要点分析1. Canvas上下文优化…

2025亲测有用 yolov8 pt转onnx转ncnn 部署安卓

参考文章&#xff1a;pt转onnx转ncnn模型&#xff08;yolov8部署安卓&#xff09;_best.pt 转ncnn模型-CSDN博客 Yolov8-Ncnn模型部署Android&#xff0c;实现单一图片识别_yolov8转ncnn-CSDN博客 onnx转化为ncnn这条路径现在已经落后了&#xff0c;更多的是通过pnnx转化为nc…

cursor的.cursorrules详解

文章目录 1. 文件位置与作用2. 基本语法规则3. 常用规则类型与示例3.1 忽略文件/目录3.2 限制代码生成范围3.3 自定义补全建议3.4 安全规则 4. 高级用法4.1 条件规则4.2 正则表达式匹配4.3 继承规则 5. 示例文件6. 注意事项 Cursor 是一款基于 AI 的智能代码编辑器&#xff0c;…

MySQL 入门大全:运算符

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

mysql中将外部文本导入表中过程出现的错误及解决方法

问题一&#xff1a; MySQL Loading local data is disabled; this must be enabled on both the client and server sides &#xff08;MySQL加载本地数据被禁用&#xff1b;这必须在客户端和服务器端同时启用&#xff09; 解决方法&#xff1a; 1&#xff0c;依次输入以下命令…

蓝牙数字音频和模拟音频优劣势对比?

蓝牙模块中我们常说的模拟音频和数字音频&#xff0c;是指两种不同的信号处理技术&#xff0c;它们都可以实现声音的录制、存储、编辑、压缩或播放&#xff0c;但也有一些区别和特点。本文将为您深入解析蓝牙数字音频和模拟音频的一些常见区别。 数字音频&#xff1a; 蓝牙数…

WiFi(无线局域网)技术的多种工作模式

WiFi&#xff08;无线局域网&#xff09;技术支持多种工作模式&#xff0c;以满足不同的网络需求和应用场景。以下是主要的WiFi工作模式及其详细说明&#xff1a; 1. 基础设施模式&#xff08;Infrastructure Mode&#xff09; [无线接入点 (AP)]/ | \ [客户端…

VMware+Ubuntu+VScode+ROS一站式教学+常见问题解决

目录 一.VMware的安装 二.Ubuntu下载 1.前言 2.Ubuntu版本选择 三.VMware中Ubuntu的安装 四.Ubuntu系统基本设置 1.中文更改 2.中文输入法更改 3. 辅助工具 vmware tools 五.VScode的安装ros基本插件 1.安装 2.ros辅助插件下载 六.ROS安装 1.安装ros 2.配置ROS…

音视频(一)ZLMediaKit搭建部署

前言 一个基于C11的高性能运营级流媒体服务框架 全协议支持H264/H265/AAC/G711/OPUS/MP3&#xff0c;部分支持VP8/VP9/AV1/JPEG/MP3/H266/ADPCM/SVAC/G722/G723/G729 1&#xff1a;环境 ubuntu22.* ZLMediaKit downlaod:https://github.com/ZLMediaKit/ZLMediaKit or https://g…

leetcode25.k个一组翻转链表

思路源自 【力扣hot100】【LeetCode 25】k个一组翻转链表&#xff5c;虚拟节点的应用 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(in…

配置 UOS/deepin 系统远程桌面,实现多台电脑协同办公

由于开发工作的需要&#xff0c;我的办公桌上目前有多台电脑。一台是 i7 配置的电脑&#xff0c;运行 UOS V20 系统&#xff0c;作为主力办公电脑&#xff0c;负责处理企业微信、OA 等任务&#xff0c;并偶尔进行代码编译和验证软件在 UOS V20 系统下的兼容性&#xff1b;另一台…

配置Next.js环境 使用vscode

配置 Next.js 的开发环境其实非常简单&#xff0c;下面是一个从零开始的完整步骤&#xff0c;适用于 Windows、macOS 和 Linux&#xff1a; ✅ 一、准备工作 确保你已经安装了以下软件&#xff1a; 1. Node.js&#xff08;推荐 LTS 版本&#xff09; 官网&#xff1a;https:/…