构造函数在C ++和/或C ++ 11中是线程安全的吗? [英] Are constructors thread safe in C++ and/or C++11?

查看:142
本文介绍了构造函数在C ++和/或C ++ 11中是线程安全的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

源自此问题

如果我在一个线程中构造一个对象,然后然后将对它的引用/指针传递给另一个线程,则该线程对该其他线程访问对象是否不安全没有显式锁定/内存屏障?

If I construct an object in one thread and then convey a reference/pointer to it to another thread, is it thread un-safe for that other thread to access the object without explicit locking/memory-barriers?

// thread 1
Obj obj;

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

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

或者:有没有合法的方式在线程之间传递数据,这些方式不强制考虑内存顺序线程碰到的所有 else 吗?从硬件的角度来看,我看不到任何不可能的原因。

Alternatively: is there any legal way to convey data between threads that doesn't enforce memory ordering with regards to everything else the thread has touched? From a hardware standpoint I don't see any reason it shouldn't be possible.

问题是关于缓存一致性,内存排序等。线程2可以在线程2的内存视图包括构造 obj 涉及的写操作之前获取并使用指针吗?要遗漏引用Alexandrescu(?)恶意CPU设计人员和编译器编写人员可能会合谋建立一个标准的,符合要求的系统吗?

To clarify; the question is with regards to cache coherency, memory ordering and whatnot. Can Thread 2 get and use the pointer before Thread 2's view of memory includes the writes involved in constructing obj? To miss-quote Alexandrescu(?) "Could a malicious CPU designer and compiler writer collude to build a standard conforming system that make that break?"

推荐答案

对线程安全性的理解可能很困难,而且我也不是C ++ 11内存模型的专家。幸运的是,您的示例非常简单。我重写了示例,因为构造函数无关。

Reasoning about thread-safety can be difficult, and I am no expert on the C++11 memory model. Fortunately, however, your example is very simple. I rewrite the example, because the constructor is irrelevant.

问题:以下代码正确吗?还是执行会导致未定义的行为

Question: Is the following code correct? Or can the execution result in undefined behavior?

// 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 的存储位置上,因此执行将导致未定义的行为

Answer: There may be a data race on the memory location of answer, and thus the execution results in undefined behavior. See below for details.

当然,答案取决于 send receive 函数的可能实现和合法实现。我使用以下无数据争用的实现。请注意,仅使用单个原子变量,并且所有内存操作均使用 std :: memory_order_relaxed 。基本上,这意味着这些函数不会限制内存的重新排序。

Of course, the answer depends on the possible and legal implementations of the functions send and receive. I use the following data-race-free implementation. Note that only a single atomic variable is used, and all memory operations use std::memory_order_relaxed. Basically this means, that these functions do not restrict memory re-orderings.

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 的原子操作不参与任何同步,也不施加任何顺序。


Order of Memory Operations

On multicore systems, a thread can see memory changes in a different order as what other threads see. In addition, both compilers and CPUs may reorder memory operations within a single thread for efficiency - and they do this all the time. Atomic operations with std::memory_order_relaxed do not participate in any synchronization and do not impose any ordering.

在上面的示例中,允许编译器对线程B的操作进行重新排序,并在B1之前执行B2,因为重新排序对线程本身没有影响。

In the above example, the compiler is allowed to reorder the operations of thread B, and execute B2 before B1, because the reordering has no effect on the thread itself.

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






数据竞赛



C ++ 11如下定义了数据争用(N3290 C ++ 11草案):程序的执行包含数据争用如果它在不同的线程中包含两个冲突的动作,其中至少一个不是 atomic ,两个都在另一个之前,那么任何此类数据竞争都会导致未定义的行为。并且在...之前发生是在同一文档中更早定义的。


Data Race

C++11 defines a data race as follows (N3290 C++11 Draft): "The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior." And the term happens before is defined earlier in the same document.

在上面的示例中,B1和A2是冲突且非原子操作,并且两者都没有发生。这是显而易见的,因为在上一节中已经表明,两者可以同时发生。

In the above example, B1 and A2 are conflicting and non-atomic operations, and neither happens before the other. This is obvious, because I have shown in the previous section, that both can happen at the same time.

这是C ++ 11中唯一重要的事情。相比之下,Java内存模型也尝试定义是否存在数据争用的行为,并且花了近十年的时间才提出合理的规范。 C ++ 11没有犯同样的错误。

That's the only thing that matters in C++11. In contrast, the Java Memory Model also tries to define the behavior if there are data races, and it took them almost a decade to come up with a reasonable specification. C++11 didn't make the same mistake.

我对这些基本知识不甚了解感到惊讶。最终的信息来源是C ++ 11标准中的多线程执行和数据争用部分。但是,该规范难以理解。

I'm a bit surprised that these basics are not well known. The definitive source of information is the section Multi-threaded executions and data races in the C++11 standard. However, the specification is difficult to understand.

汉斯·勃姆(Hans Boehm)的讲话是一个很好的起点-例如可用作在线视频:

A good starting point are Hans Boehm's talks - e.g. available as online videos:

  • Threads and Shared Variables in C++11
  • Getting C++ Threads Right

还有很多其他好处资源,我在其他地方提到过,例如:

There are also a lot of other good resources, I have mentioned elsewhere, e.g.:

  • std::memory_order - cppreference.com

这篇关于构造函数在C ++和/或C ++ 11中是线程安全的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆