news 2026/6/1 19:37:29

Go语言并发编程:核心模式总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go语言并发编程:核心模式总结

Go语言并发编程:核心模式总结

Go语言从诞生之初就将并发编程作为核心特性之一,其独特的并发模型——通过Goroutine和Channel实现的CSP(Communicating Sequential Processes)模型——使得编写高并发程序变得简单而优雅。与传统的线程模型相比,Go的并发模型更轻量、更易用、更安全。本文将深入讲解Go语言并发编程的核心模式,通过详细的代码示例帮助读者全面掌握并发编程技术。

一、Goroutine:轻量级并发基础

Goroutine是Go语言并发编程的基础单元,它是运行时管理的轻量级线程,创建成本极低,允许成千上万个Goroutine同时运行。

1.1 Goroutine基础

package main import ( "fmt" "time" ) func task(name string, duration time.Duration) { fmt.Printf("任务 %s 开始\n", name) time.Sleep(duration) fmt.Printf("任务 %s 完成\n", name) } func main() { fmt.Println("=== Goroutine 基础 ===") // 顺序执行 fmt.Println("顺序执行开始") task("A", 1*time.Second) task("B", 1*time.Second) fmt.Println("顺序执行结束\n") // 使用 Goroutine 并发执行 fmt.Println("并发执行开始") go task("A", 1*time.Second) go task("B", 1*time.Second) time.Sleep(2 * time.Second) // 等待并发任务完成 fmt.Println("并发执行结束") }

1.2 匿名Goroutine

package main import ( "fmt" "sync" ) func main() { fmt.Println("=== 匿名 Goroutine ===") var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) // 使用闭包捕获循环变量 go func(id int) { defer wg.Done() fmt.Printf("Worker %d 开始工作\n", id) // 模拟工作 for j := 0; j < 3; j++ { fmt.Printf("Worker %d: 步骤 %d\n", id, j+1) } fmt.Printf("Worker %d 完成\n", id) }(i) } wg.Wait() // 等待所有Goroutine完成 fmt.Println("所有工作完成") }

1.3 Goroutine调度原理

Go使用M:N调度模型,即M个Goroutine映射到N个操作系统线程。GOMAXPROCS设置可同时运行的逻辑处理器数量。

package main import ( "fmt" "runtime" "sync" ) func cpuIntensive() int { sum := 0 for i := 0; i < 10000000; i++ { sum += i } return sum } func main() { // 获取当前CPU核心数和GOMAXPROCS fmt.Printf("CPU核心数: %d\n", runtime.NumCPU()) fmt.Printf("当前GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) // 设置GOMAXPROCS runtime.GOMAXPROCS(4) fmt.Printf("设置后GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) var wg sync.WaitGroup start := time.Now() for i := 0; i < 8; i++ { wg.Add(1) go func() { defer wg.Done() cpuIntensive() }() } wg.Wait() fmt.Printf("耗时: %v\n", time.Since(start)) } import "time"

二、Channel:Goroutine通信桥梁

Channel是Go语言实现CSP模型的核心机制,提供Goroutine之间的通信和同步能力。

2.1 Channel基础操作

package main import ( "fmt" "time" ) func main() { fmt.Println("=== Channel 基础 ===") // 创建无缓冲Channel ch := make(chan string) // 发送数据 go func() { fmt.Println("发送者: 准备发送数据") ch <- "Hello, Channel!" fmt.Println("发送者: 数据已发送") }() // 接收数据 msg := <-ch fmt.Printf("接收者: 收到数据: %s\n", msg) // 关闭Channel close(ch) }

2.2 有缓冲Channel

package main import ( "fmt" "time" ) func main() { fmt.Println("=== 有缓冲 Channel ===") // 创建缓冲区大小为3的Channel ch := make(chan int, 3) fmt.Printf("缓冲区容量: %d\n", cap(ch)) fmt.Printf("当前元素数: %d\n", len(ch)) // 发送数据(不阻塞直到缓冲区满) for i := 1; i <= 3; i++ { ch <- i fmt.Printf("发送: %d, 缓冲区大小: %d\n", i, len(ch)) } // 尝试向已满的缓冲区发送会阻塞 go func() { time.Sleep(1 * time.Second) fmt.Println("后台: 发送数据 4") ch <- 4 }() // 接收数据 for i := 1; i <= 4; i++ { val := <-ch fmt.Printf("接收: %d, 缓冲区大小: %d\n", val, len(ch)) } }

2.3 Channel方向和关闭

package main import ( "fmt" "sync" ) func sender(ch chan<- string, msg string) { fmt.Printf("发送者: 发送 '%s'\n", msg) ch <- msg fmt.Printf("发送者: '%s' 已发送\n", msg) } func receiver(ch <-chan string, wg *sync.WaitGroup) { defer wg.Done() msg := <-ch fmt.Printf("接收者: 收到 '%s'\n", msg) } func main() { fmt.Println("=== Channel 方向 ===") ch := make(chan string, 1) var wg sync.WaitGroup wg.Add(2) go sender(ch, "Hello") go receiver(ch, &wg) wg.Wait() }

2.4 Channel遍历和关闭

package main import ( "fmt" ) func generate(ch chan<- int) { for i := 1; i <= 5; i++ { ch <- i } close(ch) // 重要:关闭Channel表示没有更多数据 fmt.Println("生成器: Channel已关闭") } func main() { fmt.Println("=== Channel 遍历 ===") ch := make(chan int, 5) go generate(ch) // 使用 range 遍历Channel,自动检测Channel关闭 fmt.Println("主程序: 开始接收数据") for val := range ch { fmt.Printf("接收到: %d\n", val) } fmt.Println("主程序: 数据接收完毕") }

三、select多路复用:并发协调核心

select语句类似于switch,但用于监控多个Channel的状态,是实现多路复用的关键。

3.1 select基础用法

package main import ( "fmt" "time" ) func main() { fmt.Println("=== select 基础 ===") ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "Channel 1 数据" }() go func() { time.Sleep(2 * time.Second) ch2 <- "Channel 2 数据" }() // 等待最先完成的Channel for i := 0; i < 2; i++ { select { case msg := <-ch1: fmt.Printf("收到: %s\n", msg) case msg := <-ch2: fmt.Printf("收到: %s\n", msg) case <-time.After(3 * time.Second): fmt.Println("超时") } } }

3.2 select超时处理

package main import ( "context" "fmt" "time" ) func processWithTimeout(ctx context.Context, duration time.Duration) error { select { case <-time.After(duration): fmt.Println("处理完成") return nil case <-ctx.Done(): return ctx.Err() } } func main() { fmt.Println("=== 超时处理 ===") // 场景1:正常完成 ctx1, cancel1 := context.WithTimeout(context.Background(), 2*time.Second) defer cancel1() fmt.Println("场景1: 2秒超时,1秒完成") processWithTimeout(ctx1, 1*time.Second) // 场景2:超时取消 ctx2, cancel2 := context.WithTimeout(context.Background(), 1*time.Second) defer cancel2() fmt.Println("\n场景2: 1秒超时,2秒完成") processWithTimeout(ctx2, 2*time.Second) }

3.3 非阻塞Channel操作

package main import ( "fmt" ) func trySend(ch chan int) { select { case ch <- 42: fmt.Println("发送成功") default: fmt.Println("Channel已满,发送失败(非阻塞)") } } func tryReceive(ch chan int) { select { case val := <-ch: fmt.Printf("接收成功: %d\n", val) default: fmt.Println("Channel为空,接收失败(非阻塞)") } } func main() { fmt.Println("=== 非阻塞操作 ===") buffered := make(chan int, 1) buffered <- 1 // 先放入一个元素 // 非阻塞发送 trySend(buffered) // 非阻塞接收 tryReceive(buffered) tryReceive(buffered) // 现在Channel为空 }

四、sync包:传统同步原语

虽然Go推崇通过Channel进行通信和同步,但sync包提供的传统同步原语在某些场景下仍然不可或缺。

4.1 WaitGroup:等待一组任务完成

package main import ( "fmt" "sync" "time" ) type Task struct { ID int Name string } func worker(t Task, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d (%s) 开始\n", t.ID, t.Name) time.Sleep(time.Duration(t.ID) * 100 * time.Millisecond) fmt.Printf("Worker %d (%s) 完成\n", t.ID, t.Name) } func main() { fmt.Println("=== WaitGroup ===") tasks := []Task{ {1, "下载"}, {2, "解压"}, {3, "编译"}, {4, "测试"}, {5, "部署"}, } var wg sync.WaitGroup wg.Add(len(tasks)) for _, task := range tasks { go worker(task, &wg) } fmt.Println("主程序: 等待所有任务完成...") wg.Wait() fmt.Println("主程序: 所有任务已完成") }

4.2 Mutex:互斥锁保护共享资源

package main import ( "fmt" "sync" ) // SafeCounter 使用互斥锁保护计数器 type SafeCounter struct { mu sync.Mutex count map[string]int } func (sc *SafeCounter) Inc(key string) { sc.mu.Lock() defer sc.mu.Unlock() sc.count[key]++ } func (sc *SafeCounter) Value(key string) int { sc.mu.Lock() defer sc.mu.Unlock() return sc.count[key] } func main() { fmt.Println("=== Mutex ===") sc := &SafeCounter{count: make(map[string]int)} var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() sc.Inc("counter") }() } wg.Wait() fmt.Printf("计数器最终值: %d\n", sc.Value("counter")) }

4.3 RWMutex:读写锁优化读多写少场景

package main import ( "fmt" "sync" "time" ) // Config 配置管理 type Config struct { rw sync.RWMutex data map[string]string } func (c *Config) Get(key string) string { c.rw.RLock() defer c.rw.RUnlock() return c.data[key] } func (c *Config) Set(key, value string) { c.rw.Lock() defer c.rw.Unlock() c.data[key] = value } func main() { fmt.Println("=== RWMutex ===") cfg := &Config{data: make(map[string]string)} var wg sync.WaitGroup // 启动多个读goroutine for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 10; j++ { val := cfg.Get("config") fmt.Printf("Reader %d: %s\n", id, val) time.Sleep(10 * time.Millisecond) } }(i) } // 启动写goroutine wg.Add(1) go func() { defer wg.Done() for i := 0; i < 10; i++ { cfg.Set("config", fmt.Sprintf("value-%d", i)) fmt.Printf("Writer: 设置为 value-%d\n", i) time.Sleep(50 * time.Millisecond) } }() wg.Wait() }

4.4 Cond:条件变量

package main import ( "fmt" "sync" "time" ) func main() { fmt.Println("=== Cond 条件变量 ===") var mu sync.Mutex cond := sync.NewCond(&mu) ready := false // 等待者 for i := 0; i < 3; i++ { go func(id int) { mu.Lock() for !ready { cond.Wait() // 等待条件满足 } mu.Unlock() fmt.Printf("Worker %d: 收到信号,开始工作\n", id) }(i) } // 信号发送者 time.Sleep(1 * time.Second) fmt.Println("主程序: 准备就绪,发送信号") mu.Lock() ready = true mu.Unlock() cond.Broadcast() // 唤醒所有等待者 time.Sleep(500 * time.Millisecond) fmt.Println("主程序: 完成") }

4.5 Once:单次执行

package main import ( "fmt" "sync" ) func main() { fmt.Println("=== Once 单次执行 ===") var once sync.Once var counter int for i := 0; i < 5; i++ { go func() { once.Do(func() { counter++ fmt.Println("初始化函数执行,计数器加1") }) fmt.Printf("Goroutine %d 访问计数器: %d\n", i, counter) }() } // 等待一下让所有goroutine执行完 time.Sleep(100 * time.Millisecond) } import "time"

4.6 Pool:对象池复用

package main import ( "fmt" "sync" "time" ) func main() { fmt.Println("=== sync.Pool 对象池 ===") // 创建对象池 pool := &sync.Pool{ New: func() interface{} { fmt.Println("创建新对象") return make([]byte, 1024) }, } // 从池中获取对象 buf1 := pool.Get().([]byte) fmt.Printf("获取对象: 长度 %d\n", len(buf1)) // 放回池中 pool.Put(buf1) // 再次获取,可能复用之前的对象 buf2 := pool.Get().([]byte) fmt.Printf("再次获取对象: 长度 %d\n", len(buf2)) // 模拟高并发场景 var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() buf := pool.Get().([]byte) // 使用buf... pool.Put(buf) }() } wg.Wait() fmt.Println("对象池测试完成") }

五、context包:上下文传播

context包提供了一种在Goroutine之间传递请求作用域的取消信号、截止时间和请求值的标准方式。

5.1 基础context使用

package main import ( "context" "fmt" "time" ) func worker(ctx context.Context, id int) { for { select { case <-ctx.Done(): fmt.Printf("Worker %d: 收到取消信号 (%s)\n", id, ctx.Err()) return default: fmt.Printf("Worker %d: 工作中...\n", id) time.Sleep(500 * time.Millisecond) } } } func main() { fmt.Println("=== Context 基础 ===") ctx, cancel := context.WithCancel(context.Background()) defer cancel() for i := 1; i <= 3; i++ { go worker(ctx, i) } time.Sleep(2 * time.Second) fmt.Println("主程序: 发送取消信号") cancel() time.Sleep(1 * time.Second) }

5.2 带超时和截止时间的context

package main import ( "context" "fmt" "time" ) func operation(ctx context.Context, name string) error { for i := 0; i < 5; i++ { select { case <-ctx.Done(): return ctx.Err() default: fmt.Printf("%s: 执行步骤 %d\n", name, i+1) time.Sleep(500 * time.Millisecond) } } return nil } func main() { fmt.Println("=== Context 超时和截止时间 ===") // 场景1:使用 WithTimeout fmt.Println("\n场景1: 2秒超时") ctx1, cancel1 := context.WithTimeout(context.Background(), 2*time.Second) defer cancel1() err1 := operation(ctx1, "操作A") if err1 != nil { fmt.Printf("操作A失败: %s\n", err1) } // 场景2:使用 WithDeadline fmt.Println("\n场景2: 截止时间(500ms后截止)") deadline := time.Now().Add(500 * time.Millisecond) ctx2, cancel2 := context.WithDeadline(context.Background(), deadline) defer cancel2() err2 := operation(ctx2, "操作B") if err2 != nil { fmt.Printf("操作B失败: %s\n", err2) } }

5.3 context值传递

package main import ( "context" "fmt" ) type traceIDKey struct{} type userIDKey struct{} func handleRequest(ctx context.Context) { // 从context中获取追踪ID traceID, ok := ctx.Value(traceIDKey{}).(string) if !ok { traceID = "unknown" } userID, ok := ctx.Value(userIDKey{}).(string) if !ok { userID = "anonymous" } fmt.Printf("处理请求 - TraceID: %s, UserID: %s\n", traceID, userID) } func main() { fmt.Println("=== Context 值传递 ===") // 创建带值的context ctx := context.Background() ctx = context.WithValue(ctx, traceIDKey{}, "abc123") ctx = context.WithValue(ctx, userIDKey{}, "user456") handleRequest(ctx) }

六、并发安全设计模式

6.1 竞态条件检测

Go提供了内置的竞态检测器,可以帮助发现并发程序中的数据竞态。

package main import ( "fmt" "sync" ) var counter int func increment() { counter++ // 这行代码存在竞态条件 } func main() { fmt.Println("=== 竞态条件检测 ===") var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait() fmt.Printf("计数器值(应该有1000): %d\n", counter) }

编译和运行竞态检测:

go build -race main.go ./main # 或者直接运行 go run -race main.go

6.2 线程安全的计数器

package main import ( "fmt" "sync" "sync/atomic" ) // Int64Counter 使用原子操作的无锁计数器 type Int64Counter struct { value int64 } func (c *Int64Counter) Increment() { atomic.AddInt64(&c.value, 1) } func (c *Int64Counter) Get() int64 { return atomic.LoadInt64(&c.value) } func main() { fmt.Println("=== 原子操作计数器 ===") var counter Int64Counter var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Printf("计数器值: %d\n", counter.Get()) }

6.3 并发安全的单例模式

package main import ( "fmt" "sync" ) type Config struct { data map[string]string } var ( config *Config once sync.Once ) // GetConfig 获取单例配置 func GetConfig() *Config { once.Do(func() { fmt.Println("初始化配置(只执行一次)") config = &Config{ data: map[string]string{ "host": "localhost", "port": "8080", }, } }) return config } func main() { fmt.Println("=== 并发安全的单例 ===") for i := 0; i < 5; i++ { go func(id int) { cfg := GetConfig() fmt.Printf("Goroutine %d: %v\n", id, cfg.data) }(i) } // 等待一下 for i := 0; i < 100; i++ { fmt.Println("") } } import "time"

6.4 生产者-消费者模式

package main import ( "fmt" "sync" "time" ) func producer(ch chan<- int, id int, count int) { for i := 0; i < count; i++ { fmt.Printf("生产者 %d: 生产数据 %d\n", id, i) ch <- i time.Sleep(100 * time.Millisecond) } } func consumer(ch <-chan int, id int, done chan<- bool) { for val := range ch { fmt.Printf("消费者 %d: 消费数据 %d\n", id, val) time.Sleep(150 * time.Millisecond) } done <- true } func main() { fmt.Println("=== 生产者-消费者模式 ===") ch := make(chan int, 10) // 缓冲区大小10 done := make(chan bool) // 启动2个生产者 for i := 0; i < 2; i++ { go producer(ch, i, 5) } // 启动3个消费者 for i := 0; i < 3; i++ { go consumer(ch, i, done) } // 等待所有消费者完成 for i := 0; i < 3; i++ { <-done } fmt.Println("所有任务完成") }

6.5 限流器模式

package main import ( "context" "fmt" "time" ) // RateLimiter 令牌桶限流器 type RateLimiter struct { rate int // 每秒生成的令牌数 burst int // 桶的容量 tokens int // 当前令牌数 lastTime time.Time // 上次添加令牌的时间 } func NewRateLimiter(rate, burst int) *RateLimiter { return &RateLimiter{ rate: rate, burst: burst, tokens: burst, lastTime: time.Now(), } } func (rl *RateLimiter) Allow() bool { return rl.AllowN(1) } func (rl *RateLimiter) AllowN(n int) bool { now := time.Now() elapsed := now.Sub(rl.lastTime) rl.lastTime = now // 根据时间补充令牌 tokensToAdd := int(elapsed.Seconds() * float64(rl.rate)) rl.tokens += tokensToAdd if rl.tokens > rl.burst { rl.tokens = rl.burst } if rl.tokens >= n { rl.tokens -= n return true } return false } func main() { fmt.Println("=== 限流器 ===") limiter := NewRateLimiter(5, 10) // 每秒5个令牌,桶容量10 for i := 0; i < 20; i++ { if limiter.Allow() { fmt.Printf("请求 %d: 允许\n", i) } else { fmt.Printf("请求 %d: 拒绝\n", i) } time.Sleep(100 * time.Millisecond) } }

6.6 并发安全的Map

package main import ( "fmt" "sync" ) func main() { fmt.Println("=== sync.Map ===") var m sync.Map // 存储键值对 m.Store("name", "张三") m.Store("age", 25) // 批量存储 m.Store("city", "北京") m.Store("email", "zhangsan@example.com") // 加载值 if val, ok := m.Load("name"); ok { fmt.Printf("name: %s\n", val) } // 加载或存储 val, loaded := m.LoadOrStore("age", 30) fmt.Printf("LoadOrStore age: %d, loaded: %v\n", val, loaded) // 遍历 fmt.Println("\n所有键值对:") m.Range(func(key, value interface{}) bool { fmt.Printf(" %s: %v\n", key, value) return true // 返回false停止遍历 }) // 删除 m.Delete("email") // 再次遍历确认删除 fmt.Println("\n删除后:") m.Range(func(key, value interface{}) bool { fmt.Printf(" %s: %v\n", key, value) return true }) }

总结

Go语言的并发编程模型是其最强大的特性之一,通过Goroutine和Channel的组合,开发者可以编写出高效、简洁的并发程序。

核心要点总结:

  1. Goroutine:轻量级并发单元,创建成本低,适合高并发场景。使用sync.WaitGroup管理生命周期。

  2. Channel:Goroutine间通信的桥梁,提供阻塞和同步机制。无缓冲Channel用于同步,有缓冲Channel用于异步通信。

  3. select:多路复用机制,同时监控多个Channel状态,是协调多个并发操作的核心。

  4. sync包:提供传统同步原语,包括互斥锁、读写锁、条件变量、Once、Pool等,适用于不同场景。

  5. context包:实现请求作用域的传递和取消信号传播,是构建可取消、可超时服务的基础。

最佳实践建议:

  1. 优先使用Channel进行并发通信和同步,遵循"不要通过共享内存来通信,应该通过通信来共享内存"的原则

  2. 使用sync.WaitGroup管理一组并发任务的等待

  3. 始终关注并发安全,使用竞态检测器(go run -race)检查程序

  4. 对于简单的计数器、标志位等,优先使用atomic包提供的原子操作

  5. 使用context传递请求级别的取消信号和超时控制

  6. 避免创建过多的Goroutine,虽然它们很轻量,但仍然消耗资源

  7. 关闭Channel时,确保没有更多的发送操作

  8. 使用有缓冲Channel时,根据预期负载设置合适的缓冲区大小

学习资源推荐:

  • Go官方博客并发系列文章:https://go.dev/blog/codelab-wiki
  • 《Go语言并发模式》官方文档:https://go.dev/doc/articles/wiki/
  • Dave Cheney的并发博客文章
  • 《Go语言实战》第八章:并发

掌握Go语言并发编程需要大量的实践,建议从简单的例子开始,逐步尝试更复杂的并发模式。在实际项目中,合理运用这些模式可以显著提升系统的性能和可靠性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/1 19:34:23

m4s-converter:轻松解锁B站缓存视频的免费转换神器

m4s-converter&#xff1a;轻松解锁B站缓存视频的免费转换神器 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经在B站缓存了心爱的视频…

作者头像 李华
网站建设 2026/6/1 19:33:48

形制与意蕴的殊途:中英诗歌的优劣差异探析

形制与意蕴的殊途&#xff1a;中英诗歌的优劣差异探析中文诗词与英文诗歌的差异&#xff0c;是表意文明与表音文明天生的文学层级差距&#xff0c;存在明确的优劣之分。中文诗词依托汉字先天优势&#xff0c;形成对称规整、章法严谨、含蓄留白、余味悠长的顶级文学特质&#xf…

作者头像 李华
网站建设 2026/6/1 19:32:34

明日方舟自动化助手终极指南:Arknights-Mower 完整使用教程

明日方舟自动化助手终极指南&#xff1a;Arknights-Mower 完整使用教程 【免费下载链接】arknights-mower 《明日方舟》长草助手 项目地址: https://gitcode.com/gh_mirrors/ar/arknights-mower 作为一名《明日方舟》玩家&#xff0c;你是否厌倦了每天重复的基建收菜、公…

作者头像 李华
网站建设 2026/6/1 19:27:12

FFXIV ACT插件终极指南:智能副本动画跳过技术深度解析

FFXIV ACT插件终极指南&#xff1a;智能副本动画跳过技术深度解析 【免费下载链接】FFXIV_ACT_CutsceneSkip 项目地址: https://gitcode.com/gh_mirrors/ff/FFXIV_ACT_CutsceneSkip FFXIV ACT辍学插件是一款专为《最终幻想14》国服玩家设计的高级工具&#xff0c;通过内…

作者头像 李华
网站建设 2026/6/1 19:23:15

深度解析RevokeMsgPatcher:企业级消息保留技术完全手册

深度解析RevokeMsgPatcher&#xff1a;企业级消息保留技术完全手册 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.c…

作者头像 李华