掌握Golang中的数据竞争检测:runtime/race包全面教程

掌握Golang中的数据竞争检测:runtime/race包全面教程

    • 引言
    • 数据竞争问题概述
      • 数据竞争的定义
      • 数据竞争对程序的影响
      • 常见数据竞争场景
    • Golang `runtime/race`包概述
      • `runtime/race`包简介
      • 启用数据竞争检测
        • 使用 `go run`
        • 使用 `go build`
        • 使用 `go test`
    • 基本用法与示例
      • 单元测试中的使用
        • 示例代码
        • 运行测试
      • 命令行中的使用
        • 使用 `go run`
        • 使用 `go build`
    • 第4章 深入理解`runtime/race`包
      • 工作原理
      • `race`检测的局限性
    • 实战案例
      • 案例一:简单数据竞争检测
        • 问题描述
        • 示例代码
        • 使用`race`包检测数据竞争
        • 解决方案与最佳实践
      • 案例二:复杂数据结构中的数据竞争
        • 问题描述
        • 示例代码
        • 使用`race`包检测数据竞争
        • 解决方案与最佳实践
    • 提高数据竞争检测效率
      • 优化代码结构
        • 示例代码
      • 使用锁和通道
        • 使用锁(mutex)
        • 使用通道(channel)
    • 常见问题与解决方案
      • 常见错误及其修复
        • 示例一:未同步的共享变量访问
        • 示例二:未同步的读写操作
      • 处理误报
    • 进阶技巧
      • 高级数据竞争检测
        • 使用条件变量
        • 使用读写锁
      • `race`包与其他工具的结合
        • 将`race`包与静态分析工具结合使用
        • 使用并发测试工具
    • 结论

在这里插入图片描述

引言

在现代软件开发中,随着多核处理器的普及并发编程已经变得越来越重要。然而,并发编程也带来了新的挑战,其中最常见的问题之一就是数据竞争(data race)。数据竞争是指两个或多个并发执行的线程访问同一个共享变量,并且至少有一个线程对该变量进行了写操作,这种情况会导致不可预知的程序行为,严重时可能会导致程序崩溃或数据不一致。

为了帮助开发者检测和解决数据竞争问题,Golang 提供了一个强大的工具包——runtime/raceruntime/race包是Golang标准库中的一部分,旨在检测并发程序中的数据竞争问题。它通过在编译和运行时插入额外的检测代码,帮助开发者快速发现和修复数据竞争问题,从而提高程序的稳定性和可靠性。

在本教程中,我们将深入探讨runtime/race包的使用方法与技巧。通过丰富的代码示例和详细的解释,我们将一步步教你如何在实际开发中利用runtime/race包检测和解决数据竞争问题。无论你是正在编写多线程程序还是处理复杂并发数据结构,本教程都将为你提供实用的指导和最佳实践。

接下来,我们将从数据竞争问题的概述开始,逐步深入了解runtime/race包的基本用法、工作原理、实战案例以及高级技巧,帮助你全面掌握这一工具的使用。

数据竞争问题概述

数据竞争的定义

数据竞争是指在并发程序中,两个或多个线程在没有适当同步的情况下同时访问同一个共享变量,并且至少有一个线程对该变量进行了写操作。由于访问顺序的不确定性,这种情况会导致不可预知的程序行为,难以调试和修复。

package mainimport ("fmt""sync"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}

在上述示例中,两个goroutine同时对共享变量counter进行递增操作,因为没有适当的同步机制,这会导致数据竞争,counter的最终值可能并不是预期的2000。

数据竞争对程序的影响

数据竞争问题对程序有多方面的影响,包括但不限于:

  1. 性能问题:由于不同线程对共享资源的争用,数据竞争会导致程序性能下降,甚至出现死锁等问题。
  2. 数据不一致性:未正确同步的数据访问会导致数据的不一致性,进而导致逻辑错误。
  3. 程序崩溃:严重的数据竞争问题可能会导致程序崩溃,尤其是在并发环境下的非法内存访问。

常见数据竞争场景

一些常见的数据竞争场景包括:

  1. 并发写操作:多个goroutine同时写入同一个共享变量。
  2. 读写操作:一个goroutine读取变量的同时,另一个goroutine写入该变量。
  3. 未同步的共享资源访问:缺乏适当的锁机制或同步原语,导致多个线程同时访问共享资源。

了解了数据竞争的定义和影响后,我们将深入探讨Golang提供的解决方案——runtime/race包,看看它是如何帮助我们检测和解决这些问题的。

Golang runtime/race包概述

runtime/race包简介

runtime/race包是Golang标准库中的一个重要组成部分,用于检测并发程序中的数据竞争问题。它通过在编译和运行时插入额外的检测代码,帮助开发者快速发现并修复数据竞争问题,从而提高程序的稳定性和可靠性。

与其他手动检测方法不同,runtime/race包能够自动检测并报告数据竞争问题,这大大减少了开发者手动查找和调试的工作量。

启用数据竞争检测

要在Golang中启用数据竞争检测,只需在编译时添加-race标志。以下是几个常见的使用方法:

使用 go run

通过 go run 命令直接运行程序并启用数据竞争检测:

go run -race main.go
使用 go build

通过 go build 命令编译程序,并启用数据竞争检测:

go build -race -o myprogram main.go
./myprogram
使用 go test

在运行测试时启用数据竞争检测:

go test -race ./...

下面是一个启用了数据竞争检测的示例代码:

package mainimport ("fmt""sync"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}

通过上述方式运行代码,如果存在数据竞争问题,程序会输出相关的检测信息,帮助开发者定位问题。

在下一章中,我们将详细介绍runtime/race包的基本用法和具体示例,帮助你更好地理解和使用这一工具。

基本用法与示例

在这一章中,我们将深入介绍runtime/race包的基本用法,并通过具体示例来展示如何在实际开发中使用该包进行数据竞争检测。

单元测试中的使用

在开发过程中,单元测试是检测数据竞争问题的一个重要环节。通过在单元测试中启用race检测,可以更早地发现并解决并发问题。

示例代码

以下是一个简单的单元测试示例,展示了如何在测试中启用race检测:

package mainimport ("sync""testing"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func TestIncrement(t *testing.T) {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()if counter != 2000 {t.Errorf("Expected counter to be 2000, but got %d", counter)}
}
运行测试

使用以下命令运行测试并启用数据竞争检测:

go test -race ./...

如果存在数据竞争问题,go test 命令会输出相关的检测信息。例如:

==================
WARNING: DATA RACE
Write at 0x00c0000a0010 by goroutine 8:main.increment()/path/to/your/code/main.go:12 +0x3cPrevious read at 0x00c0000a0010 by goroutine 7:main.TestIncrement()/path/to/your/code/main_test.go:19 +0x68
==================

通过这种方式,可以在测试阶段及早发现并解决数据竞争问题。

命令行中的使用

除了在单元测试中启用race检测外,我们还可以在命令行中使用go rungo build命令启用数据竞争检测。

使用 go run

使用 go run 命令直接运行程序并启用数据竞争检测:

go run -race main.go
使用 go build

使用 go build 命令编译程序,并启用数据竞争检测:

go build -race -o myprogram main.go
./myprogram

以下是一个示例代码,展示了如何在命令行中启用race检测:

package mainimport ("fmt""sync"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}

通过上述方式运行代码,如果存在数据竞争问题,程序会输出相关的检测信息。例如:

==================
WARNING: DATA RACE
Write at 0x00c0000a0010 by goroutine 8:main.increment()/path/to/your/code/main.go:12 +0x3cPrevious read at 0x00c0000a0010 by goroutine 7:main.main()/path/to/your/code/main.go:18 +0x68
==================

通过在命令行中启用race检测,可以方便地在开发过程中检测数据竞争问题,提高代码质量。


第4章 深入理解runtime/race

在这一章中,我们将深入探讨runtime/race包的工作原理及其局限性,帮助你更好地理解这一工具的内部机制。

工作原理

runtime/race包通过在编译时插入额外的检测代码,监控程序中所有共享变量的访问情况。在运行时,这些检测代码会记录每个goroutine对共享变量的读写操作,并检查是否存在未同步的并发访问。

具体来说,runtime/race包的工作机制包括以下几个步骤:

  1. 编译时插入检测代码:在编译阶段,Golang编译器会在每个变量访问的地方插入检测代码。这些代码用于记录当前goroutine的ID、访问的内存地址以及操作类型(读或写)。
  2. 运行时监控访问情况:在运行时,插入的检测代码会记录所有goroutine对共享变量的读写操作,并维护一个访问历史表。当一个goroutine访问某个变量时,runtime/race包会检查是否存在其他未同步的并发访问。
  3. 检测数据竞争:如果检测到未同步的并发访问,runtime/race包会报告数据竞争问题,输出详细的检测信息,包括变量的内存地址、访问的goroutine以及具体的代码位置。

race检测的局限性

虽然runtime/race包是一个强大的工具,但它也有一定的局限性:

  1. 性能开销:由于插入了额外的检测代码,启用race检测会增加程序的运行时间和内存开销。在某些性能敏感的应用中,这可能会影响程序的正常运行。
  2. 误报与漏报:尽管runtime/race包能够检测大部分数据竞争问题,但它并不能保证100%的准确性。在某些情况下,可能会出现误报或漏报,尤其是涉及复杂数据结构和高级同步原语时。
  3. 仅支持Go语言runtime/race包是Golang标准库的一部分,仅支持Go语言的程序。如果你的项目涉及其他编程语言,需要使用其他工具来检测数据竞争问题。

了解了runtime/race包的工作原理及其局限性后,我们将通过几个实际案例,展示如何在实际开发中使用这一工具检测和解决数据竞争问题。

实战案例

在这一章中,我们将通过几个具体的实战案例,展示如何使用runtime/race包检测和解决数据竞争问题。每个案例都包括问题描述、检测过程和解决方案。

案例一:简单数据竞争检测

问题描述

假设我们有一个简单的计数器程序,多个goroutine同时对计数器进行递增操作,但没有使用任何同步机制。我们希望检测并解决该程序中的数据竞争问题。

示例代码
package mainimport ("fmt""sync"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}
使用race包检测数据竞争

使用以下命令启用数据竞争检测并运行程序:

go run -race main.go

如果存在数据竞争问题,程序会输出相关的检测信息:

==================
WARNING: DATA RACE
Write at 0x00c0000a0010 by goroutine 8:main.increment()/path/to/your/code/main.go:12 +0x3cPrevious read at 0x00c0000a0010 by goroutine 7:main.main()/path/to/your/code/main.go:18 +0x68
==================
解决方案与最佳实践

为了避免数据竞争问题,我们可以使用互斥锁(mutex)来保护共享变量的访问。以下是修复后的代码:

package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutexfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mu.Lock()counter++mu.Unlock()}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}

通过在对共享变量进行操作时使用mu.Lock()mu.Unlock(),我们可以确保每次只有一个goroutine访问counter,从而避免数据竞争问题。

案例二:复杂数据结构中的数据竞争

问题描述

假设我们有一个并发访问的共享列表,多个goroutine同时对列表进行读写操作,但没有使用任何同步机制。我们希望检测并解决该程序中的数据竞争问题。

示例代码
package mainimport ("fmt""sync"
)var list []intfunc addToList(value int, wg *sync.WaitGroup) {defer wg.Done()list = append(list, value)
}func main() {var wg sync.WaitGroupwg.Add(2)go addToList(1, &wg)goaddToList(2, &wg)wg.Wait()fmt.Println("Final List:", list)
}
使用race包检测数据竞争

使用以下命令启用数据竞争检测并运行程序:

go run -race main.go

如果存在数据竞争问题,程序会输出相关的检测信息:

==================
WARNING: DATA RACE
Write at 0x00c0000a0010 by goroutine 8:main.addToList()/path/to/your/code/main.go:12 +0x3cPrevious read at 0x00c0000a0010 by goroutine 7:main.main()/path/to/your/code/main.go:18 +0x68
==================
解决方案与最佳实践

为了避免数据竞争问题,我们可以使用互斥锁(mutex)来保护共享列表的访问。以下是修复后的代码:

package mainimport ("fmt""sync"
)var list []int
var mu sync.Mutexfunc addToList(value int, wg *sync.WaitGroup) {defer wg.Done()mu.Lock()list = append(list, value)mu.Unlock()
}func main() {var wg sync.WaitGroupwg.Add(2)go addToList(1, &wg)go addToList(2, &wg)wg.Wait()fmt.Println("Final List:", list)
}

通过在对共享列表进行操作时使用mu.Lock()mu.Unlock(),我们可以确保每次只有一个goroutine访问列表,从而避免数据竞争问题。

提高数据竞争检测效率

在这一章中,我们将探讨如何通过优化代码结构、使用锁和通道等方式,提高数据竞争检测的效率。

优化代码结构

优化代码结构可以减少数据竞争问题的发生,从根本上提高程序的并发性能。以下是一些常见的优化策略:

  1. 减少共享变量的使用:尽量减少共享变量的数量,减少并发访问的机会。
  2. 局部变量优先:尽量使用局部变量代替全局变量,避免多个goroutine访问同一个变量。
  3. 分片处理:将大任务分解为多个小任务,每个任务在独立的goroutine中执行,减少共享资源的竞争。
示例代码

以下是一个优化后的示例代码,通过减少共享变量的使用,避免数据竞争问题:

package mainimport ("fmt""sync"
)func increment(wg *sync.WaitGroup, counter *int) {defer wg.Done()for i := 0; i < 1000; i++ {*counter++}
}func main() {var wg sync.WaitGroupvar counter intwg.Add(2)go increment(&wg, &counter)go increment(&wg, &counter)wg.Wait()fmt.Println("Final Counter:", counter)
}

使用锁和通道

使用互斥锁(mutex)和通道(channel)是避免数据竞争问题的常用方法。互斥锁可以确保每次只有一个goroutine访问共享变量,而通道则可以用于在goroutine之间传递数据,避免直接访问共享变量。

使用锁(mutex)

以下是一个使用互斥锁(mutex)避免数据竞争问题的示例代码:

package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutexfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mu.Lock()counter++mu.Unlock()}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}
使用通道(channel)

以下是一个使用通道(channel)避免数据竞争问题的示例代码:

package mainimport ("fmt"
)func increment(c chan int, done chan bool) {counter := 0for i := 0; i < 1000; i++ {counter++}c <- counterdone <- true
}func main() {c := make(chan int, 2)done := make(chan bool, 2)go increment(c, done)go increment(c, done)<-done<-donecounter := <-c + <-cfmt.Println("Final Counter:", counter)
}

通过将计数操作放在通道中,我们可以避免直接访问共享变量,从而避免数据竞争问题。

常见问题与解决方案

在这一章中,我们将探讨一些常见的数据竞争问题及其解决方案,帮助你在实际开发中更好地使用runtime/race包。

常见错误及其修复

示例一:未同步的共享变量访问

未同步的共享变量访问是最常见的数据竞争问题之一。以下是一个示例代码:

package mainimport ("fmt""sync"
)var counter intfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}

解决方案:使用互斥锁(mutex)同步共享变量的访问。

package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutexfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mu.Lock()counter++mu.Unlock()}
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go increment(&wg)wg.Wait()fmt.Println("Final Counter:", counter)
}
示例二:未同步的读写操作

未同步的读写操作也是常见的数据竞争问题之一。以下是一个示例代码:

package mainimport ("fmt""sync"
)var counter intfunc readCounter(wg *sync.WaitGroup) {defer wg.Done()fmt.Println("Counter:", counter)
}func increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {counter++}
}func main() {var wg sync.WaitGroupwg.Add(3)go readCounter(&wg)go increment(&wg)go readCounter(&wg)wg.Wait()
}

解决方案:使用互斥锁(mutex)同步读写操作。

package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutexfunc readCounter(wg *sync.WaitGroup) {defer wg.Done()mu.Lock()fmt.Println("Counter:", counter)mu.Unlock()
}func increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mu.Lock()counter++mu.Unlock()}
}func main() {var wg sync.WaitGroupwg.Add(3)go readCounter(&wg)go increment(&wg)go readCounter(&wg)wg.Wait()
}

处理误报

在使用runtime/race包时,可能会遇到误报问题。以下是处理误报的一些方法:

  1. 仔细检查代码:确保所有共享变量的访问都已正确同步。
  2. 使用更严格的同步机制:在某些情况下,可以使用更严格的同步机制,如条件变量(condition variable)或读写锁(RWMutex),以减少误报的可能性。
  3. 忽略误报:如果确定某些检测信息是误报,可以选择忽略这些信息,但需谨慎操作,避免遗漏真实的数据竞争问题。

进阶技巧

在这一章中,我们将探讨一些高级数据竞争检测技巧,帮助你在复杂的并发程序中更好地使用runtime/race包。

高级数据竞争检测

使用条件变量

条件变量(condition variable)是一种高级同步原语,可以在某些情况下替代互斥锁(mutex)。以下是一个使用条件变量避免数据竞争问题的示例代码:

package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutex
var cond = sync.NewCond(&mu)func increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {mu.Lock()counter++cond.Signal()mu.Unlock()}
}func readCounter(wg *sync.WaitGroup) {defer wg.Done()mu.Lock()cond.Wait()fmt.Println("Counter:", counter)mu.Unlock()
}func main() {var wg sync.WaitGroupwg.Add(2)go increment(&wg)go readCounter(&wg)wg.Wait()
}
使用读写锁

读写锁(RWMutex)是一种特殊的互斥锁,允许多个读操作同时进行,但写操作是独占的。以下是一个使用读写锁避免数据竞争问题的示例代码:

package mainimport ("fmt""sync"
)var counter int
var rw sync.RWMutexfunc increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 1000; i++ {rw.Lock()counter++rw.Unlock()}
}func readCounter(wg *sync.WaitGroup) {defer wg.Done()rw.RLock()fmt.Println("Counter:", counter)rw.RUnlock()
}func main() {var wg sync.WaitGroupwg.Add(3)go increment(&wg)go readCounter(&wg)go readCounter(&wg)wg.Wait()
}

race包与其他工具的结合

在实际开发中,可以将runtime/race包与其他数据竞争检测工具结合使用,以提高检测的准确性和全面性。

race包与静态分析工具结合使用

静态分析工具可以在代码编写阶段检测潜在的数据竞争问题,结合runtime/race包的动态检测,可以提供更全面的数据竞争检测方案。

使用并发测试工具

并发测试工具如GoConveyGinkgo等,可以帮助开发者编写和运行并发测试,结合runtime/race包,可以更有效地检测和解决并发问题。

结论

在本教程中,我们详细介绍了Golang runtime/race包的使用方法和技巧,通过丰富的代码示例和详细的解释,帮助你全面掌握这一工具的使用。通过在实际开发中使用runtime/race包,可以有效地检测和解决数据竞争问题,提高程序的稳定性和可靠性。

希望本教程对你有所帮助,并鼓励你在日常开发中积极使用runtime/race包,确保你的并发程序安全、可靠。数据竞争问题虽然复杂,但只要掌握了正确的工具和方法,就能轻松应对,编写出高质量的并发代码。

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

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

相关文章

ThreadLocal父子线程、线程池数据传递解决

多线程并发数据访问&#xff0c;确保数据安全至关重要&#xff0c;常用保证数据安全的方法有对代码synchronized锁、Lock锁&#xff0c;以及基于CAS的原子类&#xff0c;这些都是通过数据共享保障数据安全的&#xff0c;今天聊一聊另一种方案ThreadLocal线程副本&#xff0c;实…

Docker 从入门到精通全攻略

一、Docker 初印象 Docker 诞生于 2013 年&#xff0c;由 dotCloud 公司发起&#xff0c;最初是一个公司内部项目。其诞生背景源于程序员们苦于应用部署环境的复杂性&#xff0c;开发、测试、部署过程中各种库的依赖纷繁复杂&#xff0c;版本差异以及测试环境与部署环境不一致等…

WordPress设置自动更新CSS版本号

WordPress 通常会在引用 CSS 文件时添加版本号参数&#xff08;?verx.x.x&#xff09;。如果版本号未更新&#xff0c;浏览器可能继续加载旧的文件。 解决方法&#xff1a;确保你在 functions.php 文件中正确加载了 CSS 文件&#xff0c;并动态更新版本号。例如在functions.p…

达梦 DG

监视器 switchover 关于达梦DG switchover的细节&#xff0c;以下是一些关键步骤和注意事项&#xff1a; • 切换前检查确认&#xff1a; • 确认数据库版本和DG架构&#xff0c;包括IP信息及切换角色前后的情况。 • 检查DG切换方式&#xff0c;是switch over还是fail ove…

c#基本数据类型占用字节长度/取值范围/对应.net类型

具体前往&#xff1a;c#基本数据类型占用字节数/取值范围/包装类-各基本类型.net类型,占用bit位数,默认值及取值范围

多品牌NVR管理工具/设备EasyNVR多个NVR同时管理支持RTSP接入

在当今数字化浪潮席卷全球的背景下&#xff0c;视频监控行业正经历着前所未有的变革。传统的本地录像存储模式正逐步向云端集中管理转型&#xff0c;这一技术的飞跃不仅极大地提升了监控效率与安全性&#xff0c;更为各行各业的智能化管理开辟了新路径。在这一转型过程中&#…

初学者指南:知识库问答(KBQA)多跳路径的核心与应用

初学者指南&#xff1a;知识库问答&#xff08;KBQA&#xff09;多跳路径的核心与应用 知识库问答&#xff08;Knowledge Base Question Answering, KBQA&#xff09;旨在利用结构化知识库&#xff08;如Wikidata、Freebase&#xff09;回答自然语言问题。在实际应用中&#x…

利用Python爬虫获取淘宝店铺详情

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。对于电商平台&#xff0c;尤其是淘宝这样的大型电商平台&#xff0c;店铺详情数据的获取和分析对于商家来说至关重要。它不仅可以帮助商家了解市场趋势&#xff0c;还可以优化营销策略&#xff0c;提升销售业绩。本文…

卡尔曼滤波学习资料汇总

卡尔曼滤波学习资料汇总 其实&#xff0c;当初的目的&#xff0c;是为了写 MPU6050 的代码的&#xff0c;然后不知不觉学了那么多&#xff0c;也是因为好奇、感兴趣吧 有些还没看完&#xff0c;之后笔记也会同步更新的 学习原始材料 【卡尔曼滤波器】1_递归算法_Recursive P…

【HCIP]——OSPF综合实验

题目 实验需求 根据上图可得&#xff0c;实验需求为&#xff1a; 1.R5作为ISP&#xff1a;其上只能配置IP地址&#xff1b;R4作为企业边界路由器&#xff0c;出口公网地址需要通过PPP协议获取&#xff0c;并进行CHAP认证。&#xff08;PS&#xff1a;因PPP协议尚未学习&#…

MarkDown笔记记录app——待更新

1&#xff0c;语雀&#xff1a; 容易整理&#xff0c;直接上手&#xff0c;直接导出到csdn或者是github中 2&#xff0c;notion&#xff1a;注意及时清理 平时主要资料整理部分&#xff0c;注意每个page里面包含子page不能超过5MB&#xff0c; 所有的老资料笔记需要导出为htm…

vxe-table 4.9+ 实现在表格列中直接拖拽排序,列拖拽排序

Vxe UI vue vxe-table v4.9 实现在表格列中直接拖拽排序&#xff0c;列拖拽排序 安装 npm install vxe-pc-ui4.3.3 vxe-table4.9.0main.js // ... import VxeUI from vxe-pc-ui import vxe-pc-ui/lib/style.css import VxeUITable from vxe-table import vxe-table/lib/styl…

《勇者斗恶龙3:HD-2D重制版》找幽灵船攻略分享

《勇者斗恶龙3&#xff1a;HD-2D重制版》中的幽灵船是游戏里非常独特的一个区域&#xff0c;而想要找到幽灵船的话还是比较麻烦的&#xff0c;首先是听到关于幽灵船在世界海域上航行的传闻&#xff0c;包括在海盗巢穴中&#xff0c;但幽灵船的出现有一些具体条件。 勇者斗恶龙3…

《通往人工智能深度学习专家之路:全面解析学习路线图》

《通往人工智能深度学习专家之路&#xff1a;全面解析学习路线图》 一、人工智能深度学习简介1.1 人工智能与深度学习的关系1.2 深度学习的应用领域1.3 深度学习的重要性 二、深度学习路线图总览2.1 学习路线图的结构2.2 各阶段学习目标与重点 三、深度学习基础阶段3.1 数学基础…

力扣题解661 图片平滑器

题目&#xff08;简单&#xff09; 图像平滑器 是大小为 3 x 3 的过滤器&#xff0c;用于对图像的每个单元格平滑处理&#xff0c;平滑处理后单元格的值为该单元格的平均灰度。 每个单元格的 平均灰度 定义为&#xff1a;该单元格自身及其周围的 8 个单元格的平均值&#xff0c…

基于SpringBoot的“致远汽车租赁系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“致远汽车租赁系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 致远汽车租赁系统总体功能模块图 系统首页界面…

DSP28335 DMA 官方例程解析以及拓展(一)

文章目录 概述官方例程1 内部RAM to RAM Example_2833xDMA_ram_to_ram源码解析 要点方法拓展 外部固定地址 TO RAM完整程序: 对DMA 和 DMA有关的API请看这篇文章 DSP28335 DMA API介绍 概述 本篇主要分析官方提供的28335 DMA 有关的例程 在此基础上有一定的拓展 官方例程1 内…

【Java SE】JDBC

JDBC&#xff08;Java DataBase Connectivity&#xff09;是一套用于在 Java 中操作关系型数据库的 API。它允许开发者使用统一的 Java 代码来访问不同的关系型数据库。 JDBC 的本质&#xff1a;JDBC 是由官方&#xff08;Sun 公司&#xff09;定义的一套接口规范&#xff0c;…

西电数据库课设|设计学籍管理系统

前言&#xff1a;ER图和逻辑结构图不准确&#xff0c;因为在后期实际建表的过程中有改动&#xff0c;去除了一些列和外键关系&#xff0c;但是我懒得返回去改图了&#xff0c;所以还是需要自己情况画图&#xff0c;还有学生信息我忘记加性别什么的&#xff0c;这个比较简单&…

优维HAO案例:500强旗下全牌照综合性券商CMDB平台项目

撰文&#xff1a;鹿小U / 制图&#xff1a;脾气超好 某中国500强集团旗下的HS公司&#xff0c;是一家具有一定行业影响力的综合性证券公司。在近年来的发展进程中&#xff0c;该公司坚定不移地持续推进财富管理转型工作&#xff0c;将 ETF 的财富管理以及机构经纪业务作为公司…