线程安全且唯一执行嵌套函数。

时间:2022-04-23 11:47:33

I have a class MyClass whose function A is executed many times in parallel. Then, there is function B that should only be executed once. My initial setup looks simple but I doubt that it is thread-safe. How can I make it thread-safe? I'm using C++11.

我有一个类MyClass,它的函数a多次并行执行。然后,应该只执行一次函数B。我的初始设置看起来很简单,但我怀疑它是线程安全的。如何使它线程安全?我用c++ 11。

class MyClass {
    public:
        void A() {
            static bool execute_B = true;
            if (execute_B) {
                execute_B = false;
                B();
            }
        }
    private:
        void B() {
            std::cout << "only execute this once\n";
        }
};

2 个解决方案

#1


3  

This is a primary use-case for std::atomic_flag:

这是std的主要用例::atomic_flag:

class MyClass {
public:
    void A() {
        if (!execute_B_.test_and_set()) {
            B();
        }
    }

private:
    void B() {
        std::cout << "only execute this once\n";
    }

    std::atomic_flag execute_B_ = ATOMIC_FLAG_INIT;
};

Online Demo

在线演示

Note that any solutions involving static will allow only one invocation of MyClass::B, even across multiple MyClass instances, which may or may not make sense for you; assuming it doesn't make sense, this approach instead allows one invocation of MyClass::B per MyClass instance.

请注意,任何涉及静态的解决方案都只允许对MyClass进行一次调用::B,甚至跨多个MyClass实例,这可能对您有意义,也可能没有意义;假设它没有意义,这种方法可以允许对MyClass::B / MyClass实例进行一次调用。

#2


2  

Yes, your code is not thead-safe: several threads can enter inside the body of if statement before execute_B will be set to false. Also, execute_B is not atomic, so you can have problems with visibility of changes between threads.

是的,您的代码并不是安全的:在execute_B将被设置为false之前,有几个线程可以进入if语句的主体内部。另外,execute_B不是原子性的,因此您可以在线程之间的变化可见性方面存在问题。

There are many ways you can make it thread-safe. Note that version (1), (2) and (4) will block other thread from executing A past the point of B execution, until B execution is finished.

有很多方法可以使它线程安全。注意,版本(1)、(2)和(4)将阻止其他线程执行B执行的点,直到B执行完成。

1) Already mentioned std::call_once:

1)已经提到std::call_once:

void A() {
    static std::once_flag execute_B;
    std::call_once(flag1, [this](){ B(); });
}

2) Calling B as result of initializating static variable:

2)调用B作为初始化静态变量的结果:

void A() {
    static bool dummy = [this](){ B(); return true; });
}

3) Using atomic exchange:

3)使用原子交换:

void A() {
    static std::atomic<bool> execute_B = true;
    if(execute_B.exchange(false, std::memory_order_acq_rel))
        B();
}

4) Protecting check with a mutex (to avoid perfomance degradation later, use double-checked locking):

4)使用互斥锁进行保护(避免以后出现性能下降,使用双重检查锁定):

void A() {
    static std::mutex m_;
    static std::atomic<bool> execute_B = true;
    if(execute_B.load(std::memory_order_acquire)) {
        std::unique_lock<std::mutex> lk(m_);
        if(execute_B.load(std::memory_order_relaxed)) {
            B();
            execute_B.store(false, std::memory_order_release);
        }
    }
}

#1


3  

This is a primary use-case for std::atomic_flag:

这是std的主要用例::atomic_flag:

class MyClass {
public:
    void A() {
        if (!execute_B_.test_and_set()) {
            B();
        }
    }

private:
    void B() {
        std::cout << "only execute this once\n";
    }

    std::atomic_flag execute_B_ = ATOMIC_FLAG_INIT;
};

Online Demo

在线演示

Note that any solutions involving static will allow only one invocation of MyClass::B, even across multiple MyClass instances, which may or may not make sense for you; assuming it doesn't make sense, this approach instead allows one invocation of MyClass::B per MyClass instance.

请注意,任何涉及静态的解决方案都只允许对MyClass进行一次调用::B,甚至跨多个MyClass实例,这可能对您有意义,也可能没有意义;假设它没有意义,这种方法可以允许对MyClass::B / MyClass实例进行一次调用。

#2


2  

Yes, your code is not thead-safe: several threads can enter inside the body of if statement before execute_B will be set to false. Also, execute_B is not atomic, so you can have problems with visibility of changes between threads.

是的,您的代码并不是安全的:在execute_B将被设置为false之前,有几个线程可以进入if语句的主体内部。另外,execute_B不是原子性的,因此您可以在线程之间的变化可见性方面存在问题。

There are many ways you can make it thread-safe. Note that version (1), (2) and (4) will block other thread from executing A past the point of B execution, until B execution is finished.

有很多方法可以使它线程安全。注意,版本(1)、(2)和(4)将阻止其他线程执行B执行的点,直到B执行完成。

1) Already mentioned std::call_once:

1)已经提到std::call_once:

void A() {
    static std::once_flag execute_B;
    std::call_once(flag1, [this](){ B(); });
}

2) Calling B as result of initializating static variable:

2)调用B作为初始化静态变量的结果:

void A() {
    static bool dummy = [this](){ B(); return true; });
}

3) Using atomic exchange:

3)使用原子交换:

void A() {
    static std::atomic<bool> execute_B = true;
    if(execute_B.exchange(false, std::memory_order_acq_rel))
        B();
}

4) Protecting check with a mutex (to avoid perfomance degradation later, use double-checked locking):

4)使用互斥锁进行保护(避免以后出现性能下降,使用双重检查锁定):

void A() {
    static std::mutex m_;
    static std::atomic<bool> execute_B = true;
    if(execute_B.load(std::memory_order_acquire)) {
        std::unique_lock<std::mutex> lk(m_);
        if(execute_B.load(std::memory_order_relaxed)) {
            B();
            execute_B.store(false, std::memory_order_release);
        }
    }
}