Go竞争检测器(race detector)

go竞争探测器文档:Introducing the Go Race Detector

竞争条件是最隐蔽和难以捉摸的编程错误之一。它们通常会导致不稳定和神秘的故障,通常是在代码已部署到生产环境很久之后。虽然 Go 的并发机制使编写干净的并发代码变得容易,但它们并不能防止竞争条件。需要谨慎、勤奋和测试。工具可以提供帮助。

race detector 与 go 工具链集成。设置 -race 命令行标志后,编译器会使用记录访问内存的时间和方式的代码来检测所有内存访问,同时运行时库会监视对共享变量的非同步访问。当检测到这种“不雅”行为时,会打印一条警告。 (有关算法的详细信息,请参阅本文。)

由于其设计,竞争检测器只能在竞争条件实际被运行代码触发时检测到竞争条件,这意味着在实际工作负载下运行启用竞争的二进制文件非常重要。但是,启用竞争的二进制文件可以使用十倍的 CPU 和内存,因此始终启用竞争检测器是不切实际的。摆脱这种困境的一种方法是在启用竞争检测器的情况下运行一些测试。负载测试和集成测试是很好的候选者,因为它们倾向于执行代码的并发部分。另一种使用生产工作负载的方法是在正在运行的服务器池中部署一个支持竞争的实例。

竞争检测器与 Go 工具链完全集成。要在启用竞争检测器的情况下构建代码,只需将 -race 标志添加到命令行:

$ go test -race mypkg    // test the package
$ go run -race mysrc.go  // compile and run the program
$ go build -race mycmd   // build the command
$ go install -race mypkg // install the package

使用竞争检测器

要亲自试用竞争检测器,请将此示例程序复制到 racy.go 中:

package main

import "fmt"

func main() {
    done := make(chan bool)
    m := make(map[string]string)
    m["name"] = "world"
    go func() {
        m["name"] = "data race"
        done <- true
    }()
    fmt.Println("Hello,", m["name"])
    <-done
}

然后在启用竞争检测器的情况下运行它:

$ go run -race racy.go

发现上面代码存在竞争

Examples

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    start := time.Now()
    var t *time.Timer
    t = time.AfterFunc(randomDuration(), func() {
        fmt.Println(time.Now().Sub(start))
        t.Reset(randomDuration())
    })
    time.Sleep(5 * time.Second)
}

func randomDuration() time.Duration {
    return time.Duration(rand.Int63n(1e9))
}

这看起来是合理的代码,但在某些情况下它会以令人惊讶的方式失败:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x8 pc=0x41e38a]

goroutine 4 [running]:
time.stopTimer(0x8, 0x12fe6b35d9472d96)
    src/pkg/runtime/ztime_linux_amd64.c:35 +0x25
time.(*Timer).Reset(0x0, 0x4e5904f, 0x1)
    src/pkg/time/sleep.go:81 +0x42
main.func·001()
    race.go:14 +0xe3
created by time.goFunc
    src/pkg/time/sleep.go:122 +0x48

这里发生了什么?在启用竞争检测器的情况下运行程序更具启发性:

==================
WARNING: DATA RACE
Read by goroutine 5:
  main.func·001()
     race.go:16 +0x169

Previous write by goroutine 1:
  main.main()
      race.go:14 +0x174

Goroutine 5 (running) created at:
  time.goFunc()
      src/pkg/time/sleep.go:122 +0x56
  timerproc()
     src/pkg/runtime/ztime_linux_amd64.c:181 +0x189
==================

race detector 显示了问题:来自不同 goroutines 的变量 t 的不同步读写。如果初始计时器持续时间非常短,则计时器函数可能会在主 goroutine 为 t 赋值之前触发,因此对 t.Reset 的调用是用 nil t 进行的。

清风个人见解:

  • 这个错误提示是 Go 语言中的数据竞争检测器(Data Race Detector)输出的。它表示在程序运行过程中,有两个 goroutine 分别读取和写入了同一个变量,并且它们之间没有进行同步操作,从而导致了数据竞争问题。
  • 具体来说,在这个错误提示中,goroutine 5 在 main.func·001() 函数中读取了某个变量的值,而 goroutine 1 在 main.main() 函数中写入了该变量的值。由于这两个 goroutine 没有进行同步操作,因此就可能会出现读写不同步的问题,从而引发数据竞争。
  • 为了解决这个问题,可以使用 Go 语言提供的同步原语(如互斥锁、条件变量等)来保护对共享变量的访问。具体来说,可以在写入变量的代码块中获取一个互斥锁,然后再进行写入操作,最后释放锁。在读取变量的代码块中也需要获取相同的互斥锁,以确保读取到的是最新的值。这样就能够避免多个 goroutine 同时访问共享变量,从而解决了数据竞争问题。

为了修复竞争条件,我们更改代码以仅从主 goroutine 读取和写入变量 t:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    start := time.Now()
    reset := make(chan bool)
    var t *time.Timer
    t = time.AfterFunc(randomDuration(), func() {
        fmt.Println(time.Now().Sub(start))
        reset <- true
    })
    for time.Since(start) < 5*time.Second {
        <-reset
        t.Reset(randomDuration())
    }
}

func randomDuration() time.Duration {
    return time.Duration(rand.Int63n(1e9))
}
$ go run -race main.go
963.1441ms
1.0586156s
1.7398158s
1.9771881s
2.278302s
2.8400051s
3.4801887s
3.8141354s
4.0054143s
4.4983853s
5.2601257s
5.5267243s
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常好的问题。下面是使用 dlib 的 frontal_face_detector 检测器来检测人脸的代码示例: ``` import dlib # 使用 dlib 的预训练的人脸检测器模型 detector = dlib.get_frontal_face_detector() # 加载图像 image = dlib.load_rgb_image(image_file) # 检测人脸 faces = detector(image) # 打印检测到的人脸数量 print(f'Number of faces detected: {len(faces)}') # 遍历检测到的人脸 for i, face in enumerate(faces): print(f'Face {i+1}: Left: {face.left()} Top: {face.top()} Right: {face.right()} Bottom: {face.bottom()}') ``` 希望这对你有帮助。 ### 回答2: dlib 是一个功能强大的机器学习库,其中的 frontal_face_detector 是用于人脸检测的模块。下面是一个示例代码,演示如何使用 dlib 的 frontal_face_detector 来检测人脸。 ``` import dlib import cv2 # 加载dlib的预训练模型 detector = dlib.get_frontal_face_detector() # 加载图像 image = cv2.imread("image.jpg") # 转换为灰度图像 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 使用detector进行人脸检测 faces = detector(gray) # 遍历检测到的所有人脸 for face in faces: # 提取人脸边界框的坐标 x, y, w, h = face.left(), face.top(), face.width(), face.height() # 在图像上绘制人脸边界框 cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2) # 显示结果图像 cv2.imshow("Face Detection", image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在示例代码中,首先通过 dlib.get_frontal_face_detector() 创建了一个人脸检测器对象。然后加载待检测的图像,并将其转换为灰度图像。接下来,使用 detector 对灰度图像进行人脸检测,返回的结果是一个包含检测到的人脸边界框的列表。最后,遍历列表,并在图像上绘制出每个人脸的边界框。 希望以上代码对你有所帮助! ### 回答3: dlib 是一个基于 C++ 的机器学习库,提供了许多用于图像处理和人脸识别的工具。其中 frontal_face_detector 是 dlib 库中的一个人脸检测器,可用于检测图像或视频中的人脸。 以下是一个简单的示例代码,使用 frontal_face_detector 来检测人脸: ```cpp #include <dlib/image_processing/frontal_face_detector.h> #include <dlib/gui_widgets.h> #include <dlib/image_io.h> int main() { // 创建人脸检测器对象 dlib::frontal_face_detector detector = dlib::get_frontal_face_detector(); // 读取图像文件 dlib::array2d<dlib::rgb_pixel> image; dlib::load_image(image, "example.jpg"); // 使用人脸检测器检测人脸 std::vector<dlib::rectangle> faces = detector(image); // 在检测到的人脸周围画矩形框 dlib::image_window win; win.set_image(image); win.add_overlay(faces); // 显示图像及检测结果 dlib::cout << "检测到的人脸数量:" << faces.size() << dlib::endl; dlib::cout << "按任意键退出..." << dlib::endl; dlib::wait_key(); return 0; } ``` 上述代码中,首先创建了一个 frontal_face_detector 对象 detector。然后,通过 load_image 函数读取了一张图像文件。接下来,利用 detector 对象检测了图像中的人脸,并将检测到的人脸位置存储在 faces 向量中。最后,使用 image_window 和 add_overlay 函数在图像上绘制矩形框来标记检测到的人脸,并通过 cout 输出了检测到的人脸数量。wait_key 函数用于显示图像窗口并等待按键退出。 以上是使用 dlib 的 frontal_face_detector 进行人脸检测的简单示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值