构造函数在C ++å’Œ/或C ++ 11中是å¦å®‰å…¨ï¼Ÿ

时间:2012-04-20 17:39:29

标签: c++ thread-safety c++11

Derived from this question和related to this question:

如果我在一个线程中构造一个对象而然åŽå°†ä¸€ä¸ªå¼•ç”¨/指针传递给å¦ä¸€ä¸ªçº¿ç¨‹ï¼Œé‚£ä¹ˆè¯¥çº¿ç¨‹çš„其他线程在没有显å¼é”定/内存的情况下访问该对象是ä¸å®‰å…¨çš„-barriers?

// thread 1
Obj obj;

anyLeagalTransferDevice.Send(&obj);
while(1); // never let obj go out of scope

// thread 2
anyLeagalTransferDevice.Get()->SomeFn();

或者:是å¦æœ‰ä»»ä½•åˆæ³•çš„æ–¹å¼åœ¨çº¿ç¨‹ä¹‹é—´ä¼ é€’æ•°æ®ï¼Œè€Œè¿™äº›çº¿ç¨‹æ²¡æœ‰å¼ºåˆ¶æ‰§è¡Œå…³äºŽçº¿ç¨‹è§¦åŠçš„所有 else 的内存排åºï¼Ÿä»Žç¡¬ä»¶çš„角度æ¥çœ‹ï¼Œæˆ‘认为没有任何ç†ç”±ä¸å¯èƒ½ã€‚

澄清;问题是关于缓存一致性,内存排åºç­‰ç­‰ã€‚在线程2的内存视图包å«æž„造obj所涉åŠçš„写入之å‰ï¼Œçº¿ç¨‹2是å¦å¯ä»¥èŽ·å–并使用指针?错过引用Alexandrescu(?)“æ¶æ„çš„CPU设计人员和编译器编写者是å¦å¯ä»¥å…±åŒæž„建一个标准的符åˆç³»ç»Ÿæ¥å®žçŽ°è¿™ä¸€ç›®æ ‡ï¼Ÿâ€

5 个答案:

答案 0 :(得分:17)

关于线程安全的推ç†å¯èƒ½å¾ˆå›°éš¾ï¼Œè€Œä¸”我ä¸æ˜¯C ++ 11内存模型的专家。但幸è¿çš„是,你的例å­éžå¸¸ç®€å•ã€‚我é‡å†™äº†è¿™ä¸ªä¾‹å­ï¼Œå› ä¸ºæž„造函数是无关紧è¦çš„。

简化示例

问题:以下代ç æ˜¯å¦æ­£ç¡®ï¼Ÿæˆ–者执行结果å¯ä»¥æœªå®šä¹‰çš„行为?

// Legal transfer of pointer to int without data race.
// The receive function blocks until send is called.
void send(int*);
int* receive();

// --- thread A ---
/* A1 */   int* pointer = receive();
/* A2 */   int answer = *pointer;

// --- thread B ---
           int answer;
/* B1 */   answer = 42;
/* B2 */   send(&answer);
           // wait forever

答案: answer的内存ä½ç½®å¯èƒ½å­˜åœ¨æ•°æ®ç«žäº‰ï¼Œå› æ­¤æ‰§è¡Œä¼šå¯¼è‡´æœªå®šä¹‰çš„行为。有关详细信æ¯ï¼Œè¯·å‚阅下文。


æ•°æ®ä¼ è¾“的实施

当然,答案å–决于函数sendå’Œreceiveçš„å¯èƒ½å’Œåˆæ³•å®žçŽ°ã€‚我使用以下数æ®æ— ç«žäº‰å®žçŽ°ã€‚请注æ„,仅使用å•ä¸ªåŽŸå­å˜é‡ï¼Œå¹¶ä¸”所有内存æ“作都使用std::memory_order_relaxed。基本上这æ„味ç€ï¼Œè¿™äº›åŠŸèƒ½ä¸ä¼šé™åˆ¶å†…å­˜é‡æ–°æŽ’åºã€‚

std::atomic<int*> transfer{nullptr};

void send(int* pointer) {
    transfer.store(pointer, std::memory_order_relaxed);
}

int* receive() {
    while (transfer.load(std::memory_order_relaxed) == nullptr) { }
    return transfer.load(std::memory_order_relaxed);
}

内存æ“作顺åº

在多核系统上,线程å¯ä»¥æŒ‰ç…§å…¶ä»–线程看到的ä¸åŒé¡ºåºæŸ¥çœ‹å†…存更改。此外,编译器和CPU都å¯ä»¥åœ¨å•ä¸ªçº¿ç¨‹å†…é‡æ–°æŽ’åºå†…å­˜æ“作以æ高效率 - 而且他们一直这样åšã€‚使用std::memory_order_relaxed的原å­æ“作ä¸å‚与任何åŒæ­¥ï¼Œä¹Ÿä¸ä¼šå¼ºåŠ ä»»ä½•æŽ’åºã€‚

在上é¢çš„例å­ä¸­ï¼Œå…许编译器é‡æ–°æŽ’åºçº¿ç¨‹Bçš„æ“作,并在B1之å‰æ‰§è¡ŒB2,因为é‡æ–°æŽ’åºå¯¹çº¿ç¨‹æœ¬èº«æ²¡æœ‰å½±å“。

// --- valid execution of operations in thread B ---
           int answer;
/* B2 */   send(&answer);
/* B1 */   answer = 42;
           // wait forever

æ•°æ®ç«žèµ›

C ++ 11定义了一个数æ®ç«žäº‰å¦‚下(N3290 C ++ 11 Draft):“程åºçš„执行包å«æ•°æ®ç«žäº‰ï¼Œå¦‚果它在ä¸åŒçš„线程中包å«ä¸¤ä¸ªå†²çªçš„动作,其中至少有一个ä¸æ˜¯ atomic ,而å‘生在å¦ä¸€ä¸ªä¹‹å‰ã€‚任何此类数æ®ç«žäº‰éƒ½ä¼šå¯¼è‡´æœªå®šä¹‰çš„行为。“术语å‘生在之å‰åœ¨åŒä¸€æ–‡æ¡£ä¸­å®šä¹‰ä¹‹å‰ã€‚

在上é¢çš„例å­ä¸­ï¼ŒB1å’ŒA2是冲çªå’ŒéžåŽŸå­æ“作,并且都ä¸ä¼šå‘生在å¦ä¸€ä¸ªä¹‹å‰ã€‚这很明显,因为我在上一节中已ç»è¡¨æ˜Žï¼Œä¸¤è€…都å¯ä»¥åŒæ—¶å‘生。

这是C ++ 11中唯一é‡è¦çš„事情。相比之下,如果存在数æ®ç«žäº‰ï¼ŒJava内存模型也会å°è¯•å®šä¹‰è¡Œä¸ºï¼Œå¹¶ä¸”花了将近å年的时间æ‰èƒ½å¾—出åˆç†çš„规范。 C ++ 11没有犯åŒæ ·çš„错误。


更多信æ¯

我对这些基础知识并ä¸ä¸ºäººæ‰€çŸ¥æ„Ÿåˆ°æœ‰äº›æƒŠè®¶ã€‚确切的信æ¯æ¥æºæ˜¯C ++ 11标准中的多线程执行和数æ®ç«žäº‰éƒ¨åˆ†ã€‚但是,规范很难ç†è§£ã€‚

一个很好的起点是Hans Boehmçš„è°ˆè¯ - 例如å¯åœ¨çº¿è§‚看视频:

还有很多其他好的资æºï¼Œæˆ‘在其他地方已ç»æ到过,例如:

答案 1 :(得分:3)

没有对相åŒæ•°æ®çš„并行访问,因此没有问题:

  • 线程1开始执行Obj::Obj()。
  • 线程1完æˆObj::Obj()。
  • 的执行
  • 线程1将对objå ç”¨çš„内存的引用传递给线程2。
  • 线程1从ä¸å¯¹è¯¥å†…å­˜åšä»»ä½•å…¶ä»–事情(ä¸ä¹…之åŽï¼Œå®ƒä¼šé™·å…¥æ— é™å¾ªçŽ¯ï¼‰ã€‚
  • 线程2获å–objå ç”¨çš„内存的引用。
  • 线程2å¯èƒ½ä¼šå¯¹å®ƒåšä¸€äº›äº‹æƒ…,ä¸å—线程1的干扰,线程1ä»ç„¶æ— é™å¾ªçŽ¯ã€‚

唯一å¯èƒ½çš„问题是,如果Send没有作为记忆障ç¢ï¼Œé‚£ä¹ˆå®ƒå°±ä¸ä¼šçœŸæ­£æˆä¸ºâ€œåˆæ³•è½¬ç§»è®¾å¤‡â€ã€‚

答案 2 :(得分:2)

正如其他人所æ到的,构造函数ä¸æ˜¯çº¿ç¨‹å®‰å…¨çš„唯一方法是在构造函数完æˆä¹‹å‰æŸç§æ–¹å¼ä»¥æŸç§æ–¹å¼èŽ·å–指针或引用它,并且唯一的方法是构造函数本身具有将this指针注册到跨线程共享的æŸç§ç±»åž‹å®¹å™¨çš„代ç ã€‚

现在,在您的具体示例中,Branko Dimitrijevic完整地解释了您的案例是å¦æ­£å¸¸ã€‚但是在一般情况下,我会说在构造函数完æˆä¹‹å‰ä¸è¦ä½¿ç”¨æŸäº›ä¸œè¥¿ï¼Œå°½ç®¡æˆ‘认为在构造函数完æˆä¹‹å‰æ²¡æœ‰ä»»ä½•â€œç‰¹æ®Šâ€ã€‚当它进入继承链中的(最åŽä¸€ä¸ªï¼‰æž„造函数时,该对象几乎完全“好â€ï¼Œå…¶æ‰€æœ‰æˆå‘˜å˜é‡éƒ½è¢«åˆå§‹åŒ–,等等。所以没有比其他任何关键部分工作更糟糕,但是å¦ä¸€ä¸ªçº¿ç¨‹æˆ‘需è¦é¦–先了解它,并且å‘生的唯一方法是,如果你在构造函数本身中以æŸç§æ–¹å¼å…±äº«this。所以,åªæœ‰è¿™æ ·åšæ‰èƒ½æˆä¸ºâ€œæœ€åŽä¸€ä»¶äº‹â€ã€‚

答案 3 :(得分:1)

如果您编写了两个线程,并且知é“第一个线程在第二个线程没有访问它时,它是安全的(有点)。例如,如果构造它的线程在传递引用/指针åŽä»Žä¸è®¿é—®å®ƒï¼Œé‚£ä¹ˆä½ å°±å¯ä»¥äº†ã€‚å¦åˆ™å®ƒæ˜¯çº¿ç¨‹ä¸å®‰å…¨çš„。您å¯ä»¥é€šè¿‡ä½¿è®¿é—®æ•°æ®æˆå‘˜ï¼ˆè¯»å–或写入)的所有方法é”定内存æ¥æ›´æ”¹å®ƒã€‚

答案 4 :(得分:0)

直到现在æ‰è¯»åˆ°è¿™ä¸ªé—®é¢˜......ä»ç„¶ä¼šå‘表我的评论:

é™æ€å±€éƒ¨å˜é‡

当您处于多线程环境中时,有一ç§å¯é çš„方法æ¥æž„造对象,å³ä½¿ç”¨é™æ€å±€éƒ¨å˜é‡ï¼ˆstatic local variable-CppCoreGuidelines),

从上é¢çš„引用中å¯ä»¥çœ‹å‡ºï¼šâ€œè¿™æ˜¯ä¸Žåˆå§‹åŒ–顺åºç›¸å…³çš„问题最有效的解决方案之一。在多线程环境中,é™æ€å¯¹è±¡çš„åˆå§‹åŒ–ä¸ä¼šå¼•å…¥ç«žäº‰æ¡ä»¶ï¼ˆé™¤éžæ‚¨ä¸å°å¿ƒè®¿é—®å…±äº«å¯¹è±¡ï¼‰æ¥è‡ªå…¶æž„造函数)。“

å¦è¯·æ³¨æ„,如果X的销æ¯æ¶‰åŠéœ€è¦åŒæ­¥çš„æ“作,则å¯ä»¥åœ¨å †ä¸Šåˆ›å»ºå¯¹è±¡å¹¶åŒæ­¥ä½•æ—¶è°ƒç”¨æžæž„函数。

下é¢æ˜¯æˆ‘写的一个例å­ï¼Œæ˜¾ç¤ºConstruct On First Use Idiom,这基本上就是å‚考文献所说的。

#include <iostream>
#include <thread>
#include <vector>

class ThreadConstruct
{
public:
    ThreadConstruct(int a, float b) : _a{a}, _b{b}
    {
        std::cout << "ThreadConstruct construct start" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "ThreadConstruct construct end" << std::endl;
    }

    void get()
    {
        std::cout << _a << " " << _b << std::endl;
    }

private:
    int _a;
    float _b;
};


struct Factory
{
    template<class T, typename ...ARGS>
    static T& get(ARGS... args)
    {
        //thread safe object instantiation
        static T instance(std::forward<ARGS>(args)...);
        return instance;
    }
};

//thread pool
class Threads
{
public:
    Threads() 
    {
        for (size_t num_threads = 0; num_threads < 5; ++num_threads) {
            thread_pool.emplace_back(&Threads::run, this);
        }
    }

    void run()
    {
        //thread safe constructor call
        ThreadConstruct& thread_construct = Factory::get<ThreadConstruct>(5, 10.1);
        thread_construct.get();
    }

    ~Threads() 
    {
        for(auto& x : thread_pool) {
            if(x.joinable()) {
                x.join();
            }
        }
    }

private:
    std::vector<std::thread> thread_pool;
};


int main()
{
    Threads thread;

    return 0;
}

输出:

ThreadConstruct construct start
ThreadConstruct construct end
5 10.1
5 10.1
5 10.1
5 10.1
5 10.1