如下同样逻辑的代码,用 C++ 实现与用 Go 实现结果完全不一样. C++ 代码能够正常运行,而 Golang 代码直接死锁(即使强制调度也是死锁).
简单分析:
C/C++ 锁粒度是线程级别,线程调度由内核提供. Golang 锁粒度为协程级别,多个协程的底层运行可能会在同一个M上,而且协程调度会导致执行一半的代码被挂起,会导致两个协程同时满足条件进入等待,从而产生死锁.
package main
import (
"runtime"
"sync"
"time"
)
var flag bool = true
var wg = &sync.WaitGroup{}
var cond = sync.NewCond(&sync.Mutex{})
func test_cond(idx int, v bool) {
for i := 0; i < 3; i++ {
cond.L.Lock()
defer cond.L.Unlock()
for flag == v {
println(idx, "is waitting")
cond.Wait()
println(idx, "finish waitting")
}
flag = !flag
println("routine", idx, "is running")
time.Sleep(time.Millisecond * 100)
cond.Broadcast()
runtime.Gosched()
}
wg.Done()
println("routine", idx, "quit")
}
func main() {
wg.Add(2)
go test_cond(0, true)
go test_cond(1, false)
wg.Wait()
}
自己仿照 Golang 的 WaitGroup 的实现.
#ifndef _WAIT_GROUP_HPP
#define _WAIT_GROUP_HPP
namespace std
{
#include <condition_variable>
#include <mutex>
class wait_group
{
private:
mutex _m;
condition_variable _cond;
int _wait_count;
public:
wait_group(int count = 0) : _wait_count(count) {}
~wait_group() {}
void add(int count)
{
lock_guard<std::mutex> _lk(_m);
_wait_count += count;
}
void set(int count)
{
lock_guard<std::mutex> _lk(_m);
_wait_count = count;
}
void wait()
{
unique_lock<std::mutex> _lk(_m);
while (_wait_count)
{
_cond.wait(_lk);
}
_cond.notify_all();
}
int wait_for(int milliseconds)
{
unique_lock<std::mutex> _lk(_m);
while (_wait_count)
{
auto ret = _cond.wait_for(_lk, chrono::milliseconds(milliseconds));
if (ret == cv_status::timeout)
return _wait_count;
}
_cond.notify_all();
return 0;
}
void done()
{
lock_guard<std::mutex> _lk(_m);
if (_wait_count)
_wait_count--;
if (!_wait_count)
_cond.notify_all();
}
};
};
#endif //_WAIT_GROUP_HPP
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <thread>
#include "wait_group.hpp"
using namespace std;
condition_variable cond;
mutex m;
wait_group wg;
bool flag = true;
void test_cond(int idx, bool wv)
{
for (int idx = 0; idx < 3; idx++)
{
std::unique_lock<std::mutex> _lk(m);
while (flag == wv)
{
if (cond.wait_for(_lk, chrono::seconds(10)) == cv_status::timeout)
cerr << "timeout" << endl;
}
flag = !flag;
cerr << "thread: " << idx << " is running" << endl;
this_thread::sleep_for(chrono::milliseconds(100));
cond.notify_all();
}
wg.done();
cerr << "thread: " << idx << " quit" << endl;
}
int main()
{
wg.set(2);
thread t1(test_cond, 0, true);
t1.detach();
thread t2(test_cond, 1, false);
t2.detach();
wg.wait();
return 0;
}