You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

两种std::atomic_flag初始化与使用方法的优劣对比及技术疑问

关于两种atomic_flag线程通知方法的分析与疑问

我看到两种基于std::atomic_flag的线程通知实现写法:

  • METHOD 1:在main()中将两个flag设为true,线程调用wait()等待true状态;
  • METHOD 2:main中不预先设置flag,线程等待false状态。

当main将第一个flag设为对应状态后,线程被唤醒,执行clear()或test_and_set(),随后进入临界区递增共享变量data。两种方法本质都是线程间的通知机制,仅flag的初始状态和等待条件相反,最终main都能返回预期值13。


METHOD 1

#include <atomic>
#include <thread>
#include <functional>


void func1( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( true );
    input_data_flag.test_and_set( std::memory_order_acquire );

    data += 1;

    // Prepare the second flag and notify the second thread
    output_data_flag.clear( std::memory_order_release );
    output_data_flag.notify_one( );
}

void func2( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( true );
    input_data_flag.test_and_set( std::memory_order_acquire );

    data += 2;

    output_data_flag.clear( std::memory_order_release );
    output_data_flag.notify_one( );

}

int main( )
{
    std::atomic_flag flag1 { };
    std::atomic_flag flag2 { };

    flag1.test_and_set( std::memory_order_acquire );
    flag2.test_and_set( std::memory_order_acquire );

    int data { 10 };

    {
    std::jthread func1_thread { func1, std::ref( data ),
                                std::ref( flag1 ), std::ref( flag2 ) };
    
    std::jthread func2_thread { func2, std::ref( data ),
                                std::ref( flag2 ), std::ref( flag1 ) };

// here the first thread is finally notified to start its task
    flag1.clear( std::memory_order_release );
    flag1.notify_one( );
    }

    return data;
}

METHOD 2

#include <atomic>
#include <thread>
#include <functional>


void func1( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( false );
    input_data_flag.clear( std::memory_order_release );

    data += 1;

    // Prepare the second flag and notify the second thread
    output_data_flag.test_and_set( std::memory_order_acquire );
    output_data_flag.notify_one( );
}

void func2( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( false );
    input_data_flag.clear( std::memory_order_release );

    data += 2;

    output_data_flag.test_and_set( std::memory_order_acquire );
    output_data_flag.notify_one( );
}

int main( )
{
    std::atomic_flag flag1 { };
    std::atomic_flag flag2 { };

    int data { 10 };

    {
    std::jthread func1_thread { func1, std::ref( data ),
                                std::ref( flag1 ), std::ref( flag2 ) };
    
    std::jthread func2_thread { func2, std::ref( data ),
                                std::ref( flag2 ), std::ref( flag1 ) };

// here the first thread is finally notified to start its task
    flag1.test_and_set( std::memory_order_acquire );
    flag1.notify_one( );
    }

    return data;
}

疑问点

  1. 哪种方法效率更高?或是二者本质等价(尽管方法1编译后多5行汇编)?
  2. 方法2是否合理?线程等待flag变为true后用clear( std::memory_order_release )将其设为false再进入临界区,这似乎有问题。尤其是写入共享资源前使用std::memory_order_release内存序,是否应该换成std::memory_order_acquire?方法2应该使用哪些正确的内存序?

解答

效率与等价性

两种方法本质等价,效率差异可以忽略不计。汇编行数的微小差异来自初始设置flag的操作,但在实际运行中,std::atomic_flag的操作都是原子指令,性能开销几乎一致。两者的核心逻辑都是通过原子flag的状态变化配合wait/notify实现线程间的同步,没有本质的性能优劣之分。

方法2的合理性与内存序修正

方法2的原始写法存在内存序误用,需要修正:

  • 线程被唤醒后,在访问共享资源data之前,需要确保之前的同步操作完成,因此应该使用std::memory_order_acquire语义,而非release。release语义用于确保当前线程的写操作对其他线程可见,而acquire用于确保后续的读/写操作能看到之前的同步状态。
  • 方法2中,线程调用input_data_flag.wait(false)后,正确的操作应该是用test_and_set(std::memory_order_acquire)(而非clear)来标记flag已被处理,避免其他线程误触发;而在通知下一个线程时,应该用clear(std::memory_order_release)来释放同步语义,确保当前线程对data的修改对下一个线程可见。

修正后的方法2代码示例:

#include <atomic>
#include <thread>
#include <functional>

void func1( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( false );
    // 用acquire语义确保后续访问data能看到同步状态
    input_data_flag.test_and_set( std::memory_order_acquire );

    data += 1;

    // 用release语义确保data的修改对下一个线程可见
    output_data_flag.clear( std::memory_order_release );
    output_data_flag.notify_one( );
}

void func2( int& data,
            std::atomic_flag& input_data_flag,
            std::atomic_flag& output_data_flag )
{
    input_data_flag.wait( false );
    input_data_flag.test_and_set( std::memory_order_acquire );

    data += 2;

    output_data_flag.clear( std::memory_order_release );
    output_data_flag.notify_one( );
}

int main( )
{
    std::atomic_flag flag1 { }; // 默认初始为false
    std::atomic_flag flag2 { };

    int data { 10 };

    {
    std::jthread func1_thread { func1, std::ref( data ),
                                std::ref( flag1 ), std::ref( flag2 ) };
    
    std::jthread func2_thread { func2, std::ref( data ),
                                std::ref( flag2 ), std::ref( flag1 ) };

    // 设置flag1为true,唤醒func1线程,用release语义确保状态变化可见
    flag1.test_and_set( std::memory_order_release );
    flag1.notify_one( );
    }

    return data;
}

修正后,方法2的逻辑就合理了:线程等待flag从初始的false变为true(main调用test_and_set设置),唤醒后用acquire语义建立同步关系,确保后续对data的修改是安全的;通知下一个线程时用release语义,确保当前修改对下一个线程可见。


内容的提问来源于stack exchange,提问作者digito_evo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:30:15