前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Chromium】Base库的ConditionVariable

【Chromium】Base库的ConditionVariable

原创
作者头像
lealc
发布2024-04-02 09:55:57
1050
发布2024-04-02 09:55:57
举报
文章被收录于专栏:Chromium学习Chromium学习

源码

先附上可用于学习的开源代码:Base库

喜欢可以帮忙Star一下

前言

编译:参考Base库即可

环境:Visual Studio 2022 - 17.8.3 + v143 + 10.0.22621.0 + C++17

ConditionVariable

base::ConditionVariable 是 Chromium 的 base 库中的一个类,用于线程间的条件变量通信和同步。

条件变量是一种线程同步机制,允许线程在满足特定条件之前等待,直到其他线程发出信号通知条件已满足。base::ConditionVariable 提供了一个接口,允许线程等待条件的满足和通知其他线程。

ConditionVariable 封装了 pthreads 条件变量同步,或者在 Windows 上模拟它。这个功能在多个线程等待事件的情况下非常有用,比如由主线程管理的线程池。在线程池场景中,事件的含义是有额外的任务可用于处理。在 Chrome 中,它用于 DNS 预取系统,通知工作线程一个队列中现在有需要处理的项目(任务)。

另一个相关的用例是,线程池管理器等待 ConditionVariable,等待池中的线程通知(信号)通信队列中有更多空间可供管理器存放任务,或者作为第二个示例,任务队列完全为空且所有工作线程都在等

注意事项

1、检查信号状态

这种实现和大多数条件变量的实现都可能存在虚假的信号事件。

因此,在继续之前务必重新检查条件。下面是正确的做法:

代码语言:C++
复制
while (!work_to_be_done()) Wait(...);

相反,不要这样:

代码语言:C++
复制
if (!work_to_be_done()) Wait(...);  // 不要这样

针对依赖其他线程发出信号才执行的工作线程,更应该避免上述问题。

可能会有虚假的信号。在等待线程中,在假设信号是激活的之前,请重新检查信号的状态。

调用 Broadcast()时会向所有线程发出信号

2、唤醒策略

Broadcast() 一次释放所有等待的线程,导致它们在调用 Wait() 时都持有锁,从而导致竞争。这会导致性能差。最佳实践是让每个线程在退出 Wait() 后调用 Signal() 来释放另一个等待的线程。

Broadcast() 可以在退出时起到非常不错的作用,至少可以避免还存在休眠的线程,在这方面性能就不是很关键了。

Broadcast() 的语义经过精心设计,以确保在发出请求时等待的 所有 线程都会被发出信号。有些实现会出错,不能全部发出信号,而其他实现则允许等待在一段时间内被有效关闭(当等待线程再次到来时)。这个实现似乎是正确的,因为它不会“丢失”任何信号,并且保证所有等待的线程都会被唤醒。

选择要唤醒的线程时支持“performance”策略。performance策略与“fairness”策略相对,它确保通过 Signal 选择最近开始 Wait() 的线程来唤醒。如果设置为fairness策略,它将确保选择等待时间最长的线程。默认策略可能会提高性能,因为选择的线程具有更大的堆栈数据在CPU上。

源码

老规矩先上源码

代码语言:C++
复制
class BASE_EXPORT ConditionVariable {
public:
    // 为一个用户锁构造一个条件变量。
    explicit ConditionVariable(Lock* user_lock);

    ~ConditionVariable();

    // Wait() 在开始睡眠时原子性地释放调用者的临界区,并在收到信号时重新获取它。等待函数容易受到虚假唤醒的影响(有关更多详细信息,请参见使用注意事项1)。
    void Wait();
    void TimedWait(const TimeDelta& max_time);

    // Broadcast() 唤醒所有等待的线程(有关更多详细信息,请参见使用注意事项2)。
    void Broadcast();
    // Signal() 唤醒一个等待的线程。
    void Signal();

    // 声明此 ConditionVariable 只会被一个空闲线程在堆栈底部使用,并在等待工作时(特别是在恢复进行中的工作之前)不会同步等待此 ConditionVariable。这对于避免告诉基础内部此线程被“阻塞”,而实际上它只是空闲并准备好工作是有用的。因此,这仅预期由线程和线程池实现使用。
    void declare_only_used_while_idle() { waiting_is_blocking_ = false; }

private:
#if defined(OS_WIN)
    CHROME_CONDITION_VARIABLE cv_;
    CHROME_SRWLOCK* const srwlock_;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
    pthread_cond_t condition_;
    pthread_mutex_t* user_mutex_;
#endif

#if DCHECK_IS_ON()
    base::Lock* const user_lock_;  // 在等待时需要调整阴影锁状态。
#endif

    // 一个线程在调用此 ConditionVariable 上的 Wait() 时,是否应被视为被阻塞而不是空闲(如果是线程池的一部分,则可能被替换)。
    bool waiting_is_blocking_ = true;

    DISALLOW_COPY_AND_ASSIGN(ConditionVariable);
}

对应的实现如下:

代码语言:C++
复制
ConditionVariable::ConditionVariable(Lock* user_lock)
    : srwlock_(user_lock->lock_.native_handle())
#if DCHECK_IS_ON()
    , user_lock_(user_lock)
#endif
{
    DCHECK(user_lock);
    InitializeConditionVariable(reinterpret_cast<PCONDITION_VARIABLE>(&cv_));
}

ConditionVariable::~ConditionVariable() = default;

void ConditionVariable::Wait() {
    TimedWait(TimeDelta::FromMilliseconds(INFINITE));
}

void ConditionVariable::TimedWait(const TimeDelta& max_time) {
    Optional<internal::ScopedBlockingCallWithBaseSyncPrimitives>
        scoped_blocking_call;
    if (waiting_is_blocking_)
        scoped_blocking_call.emplace(BlockingType::MAY_BLOCK);

    DWORD timeout = static_cast<DWORD>(max_time.InMilliseconds());

#if DCHECK_IS_ON()
    user_lock_->CheckHeldAndUnmark();
#endif

    if (!SleepConditionVariableSRW(reinterpret_cast<PCONDITION_VARIABLE>(&cv_),
        reinterpret_cast<PSRWLOCK>(srwlock_), timeout,
        0)) {
        // 在失败的情况下,我们只期望条件变量超时。任何其他错误值都意味着我们意外地被唤醒。 
        // 注意,WAIT_TIMEOUT != ERROR_TIMEOUT。WAIT_TIMEOUT 用于 WaitFor* 函数族作为直接返回值。ERROR_TIMEOUT 用于 GetLastError()。
        DCHECK_EQ(static_cast<DWORD>(ERROR_TIMEOUT), GetLastError());
    }

#if DCHECK_IS_ON()
    // Debug模式下,还会检查当前超时被唤醒时线程是否符合预期等待前的线程
    user_lock_->CheckUnheldAndMark();
#endif
}

void ConditionVariable::Broadcast() {
    WakeAllConditionVariable(reinterpret_cast<PCONDITION_VARIABLE>(&cv_));
}

void ConditionVariable::Signal() {
    WakeConditionVariable(reinterpret_cast<PCONDITION_VARIABLE>(&cv_));
}

解析

  1. 跟base::Lock相同,也是借助了windows平台底层的SRW锁来实现的条件变量
  2. 仅在Debug模式下,base::ConditionVariable需要借助base::Lock进行初始化
  3. 注意这里Wait函数调用的是无限等待,一定要有兜底逻辑,避免线程无法退出

使用示例

代码语言:C++
复制
Lock lock;
ConditionVariable cv(&lock);
lock.Acquire();

TimeTicks start = TimeTicks::Now();
const TimeDelta WAIT_TIME = TimeDelta::FromMilliseconds(300);
const TimeDelta FUDGE_TIME = TimeDelta::FromMilliseconds(50);

cv.TimedWait(WAIT_TIME + FUDGE_TIME);
TimeDelta duration = TimeTicks::Now() - start;

// GTest框架
EXPECT_TRUE(duration >= WAIT_TIME);

lock.Release();

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码
  • 前言
  • ConditionVariable
  • 注意事项
    • 1、检查信号状态
      • 2、唤醒策略
      • 源码
        • 使用示例
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档