C++ New Operator Thread Safety in Linux and Gcc 4

C++ new operator thread safety in linux and gcc 4

You will have to look very hard to find a platform that supports threads but doesn't have a thread safe new. In fact, the thread safety of new (and malloc) is one of the reasons it's so slow.

If you want a thread safe STL on the other hand, you may consider Intel TBB which has thread aware containers (although not all operations on them are thread safe).

Thread safety for overloaded operator new

It is.

However note that in C++11 new is thread safe.

Of course, when you add thread unsafe code, it makes your operator new thread unsafe.

Following your edit (which changed the whole question):

The assumption that the compiler adds thread safety code around the new call is pretty wrong. Sane implementations will always add thread safety within the inner implementation of operator new (already because of efficiency considerations like per-thread memory pools).

That is, when you write a thread unsafe allocation function, just by naming it operator new will not magically make it thread safe, as this is just like any other function, only with a special way to be invoked.

new and delete handles multithreading issues

It will depend on implementation. For example, Visual C++ runtime had both a single-threaded and a multithreaded version of heap in earlier version, but starting with Visual C++ 2005 it only has a multithreaded version. This MSDN article has a nice summary table.

When a multithreaded heap is used calls to memory allocation and deallocation are thread-safe at expense of additional overhead.

Are constructors thread safe in C++ and/or 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.

Simplified Example

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: There may be a data race on the memory location of answer, and thus the execution results in undefined behavior. See below for details.


Implementation of Data Transfer

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);
}

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.

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

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.

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.

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.


Further Information

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.

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

Is malloc thread-safe?

I read somewhere that if you compile with -pthread, malloc becomes thread safe. I´m pretty sure its implementation dependant though, since malloc is ANSI C and threads are not.

If we are talking gcc:

Compile and link with -pthread and
malloc() will be thread-safe, on x86
and AMD64.

http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2431a99b9bdcef11/ea800579e40f7fa4

Another opinion, more insightful

{malloc, calloc, realloc, free,
posix_memalign} of glibc-2.2+ are
thread safe

http://linux.derkeiler.com/Newsgroups/comp.os.linux.development.apps/2005-07/0323.html

atomic_init is not thread safe in C, so why does it exist?

Atomic values may have hidden fields used to achieve the desired semantics, and those need to be initialized somehow. This can be achieved using an C variable initializer or using atomic_init.

For an static or automatic variable, that provides two options:

_Atomic int x = 7;
_Atomic int x; 
atomic_init( &x, 7 );

But that only leaves one option for dynamically-allocated variables.

_Atomic int *p = malloc( sizeof( _Atomic int ) );
atomic_init( p, 7 );

C++11 shared pointer thread safety is broken?

No. = and reset are not thread safe. The shared_ptr overloads of std::atomic_... functions are needed.

"Control block is thread-safe" means you can use = and reset in multiple threads (but each thread uses a separate shared_ptr), even if all shared_ptr objects are copies of each other.

GCC's TSAN reports a data race with a thread safe static local

is TSan buggy ? (When I am using Clang's toolchain, I get no data race report)
does GCC emit code which is not thread safe? (I am not using -fno-threadsafe->statics though)
is my understanding of static locals incorrect?

I believe this is bug in gcc part that generate code for tsan purposes.

I try this:

#include <thread>
#include <iostream>
#include <string>

std::string message()
{
static std::string msg("hi");
return msg;
}

int main()
{
std::thread t1([]() { std::cout << message() << "\n"; });
std::thread t2([]() { std::cout << message() << "\n"; });

t1.join();
t2.join();
}

If look at code generate by clang and gcc, all good,
__cxa_guard_acquire is called in both cases for path that init static local variable. But in case of check that we need init msg or not we have problem.

The code looks like this

if (atomic_flag/*uint8_t*/) {
lock();
call_constructor_of_msg();
unlock();
}

in case of clang callq __tsan_atomic8_load was generated,
but in the case of gcc it generate callq __tsan_read1.
Note that this calls annotate real memory operations,
not do operations by itself.

so it at runtime tsan runtime library thinks that all bad,
and we have data race, I report problem here:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68338

and looks like it fixed in trunk, but not in current stable release of gcc - 5.2



Related Topics



Leave a reply



Submit