I'm aware of the fact that the containers from standard library are not thread-safe. By that I used to think that a container, say of type std::list, cannot be accessed by more than one thread concurrently (some of which may modify the container). But now it seems that there is more to it than meets the eye; something more subtle, something not so obvious, well at least to me.
For example, consider this function which accepts the first argument by value:
void log(std::string msg, severity s, /*...*/)
{
return; //no code!
}
Is this thread-safe?
At first, it seems that it is thread-safe, as the function body accesses no shared modifiable resources, hence thread-safe. On second thought, it comes to me that when invoking such a function, an object of type std::string will be created, which is the first argument, and I think that construction of this object isn't thread-safe, as it internally uses std::allocator, which I believe isn't thread-safe. Hence invoking such a function isn't thread-safe either. But if it is correct, then what about this:
void f()
{
std::string msg = "message"; //is it thread-safe? it doesn't seem so!
}
Am I going right? Can we use std::string (or any container which uses std::allocator internally) in multi-threaded program?
I'm specifically talking about containers as local variables, as opposed to shared objects.
I searched google and found many similar doubts, with no concrete answer. I face similar problem as his:
c++ allocators thread-safe?
Please consider C++03 and C++11, both.
In C++11, std::allocator is thread safe. From its definition:
20.6.9.1/6: Remark: the storage is obtained by calling ::operator new(std::size_t)
and from the definition of ::operator new:
18.6.1.4: The library versions of operator new and operator delete, user replacement versions of global operator new and operator delete, and the C standard library functions calloc, malloc, realloc, and free shall
not introduce data races (1.10) as a result of concurrent calls from different threads.
C++03 had no concept of threads, so any thread safety was implementation-specific; you'd have to refer to your implementation's documentation to see what guarantees it offered, if any. Since you're using Microsoft's implementation, this page says that it is safe to write to multiple container objects of the same class from many threads, which implies that std::allocator is thread-safe.
In C++11 this would be addressed for the default allocator in:
20.6.9.1 allocator members [allocator.members]
Except for the destructor, member functions of the default allocator
shall not introduce data races (1.10) as a result of concurrent calls
to those member functions from different threads. Calls to these
functions that allocate or deallocate a particular unit of storage
shall occur in a single total order, and each such deallocation call
shall happen before the next allocation (if any) in this order.
Any user-provided allocator would have to hold to the same constraints if it were going to be used across different threads.
Of course, for earlier versions of the standard, nothing is said about this since they didn't talk about multithreading. If an implementation were to support multithreading (as many or most do), it would be responsible for taking care of those issues. Similar to the way implementations provide a thread-safe malloc() (and other library functions) for C and C++ even though the standards prior to very recently said nothing about that.
As you may have already figured, there is not going to be an easy yes or no answer. However, I think this may help:
http://www.cs.huji.ac.il/~etsman/Docs/gcc-3.4-base/libstdc++/html/faq/index.html#5_6
I quote verbatim:
5.6 Is libstdc++-v3 thread-safe?
libstdc++-v3 strives to be thread-safe when all of the following
conditions are met:
The system's libc is itself thread-safe,
gcc -v reports a thread model other than 'single',
[pre-3.3 only] a non-generic implementation of atomicity.h exists for the architecture in question.
When an std::string is copied during call to log, the allocator may be thread-safe (mandatory in C++11), but the copy itself isn't. So if there is another thread mutating the source string while copy is taking place, this is not thread safe.
You may end-up with half the string as it was before mutation and another half after, or may even end-up accessing deallocated memory if the mutating thread reallocated (e.g. by appending new characters) or deleted the string, while the copy was still taking place.
OTOH, the...
std::string msg = "message";
...is thread safe provided your allocator is thread safe.
Related
Reading C++11 FAQ -- Threads there's this paragraph which I don't understand:
Consequently, C++11 provides some rules/guarantees for the programmer to avoid data races:
A C++ standard library function shall not directly or indirectly access objects accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's arguments, including this.
A C++ standard library function shall not directly or indirectly modify objects accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's nonconst arguments, including this.
C++ standard library implementations are required to avoid data races when different elements in the same sequence are modified concurrently.
I know what a data race is, and multithreading generally, but I don't understand what these sentences are saying.
Could you explain them more clearly? Perhaps with an example? What is or isn't it safe for me (i.e. an application programmer) to do in a multi-threaded context?
Had I not read these, I would have guessed that it's not safe to have multiple threads calling a non-const method of an object of any type, but I suppose this is saying something in addition to that?
OK I think I figured it out from the comments (please correct me if I'm wrong).
These first two are similar -- i.e. that a function will only read or write memory that's reachable via the function argument.
Perhaps this means, no more and no less than, that functions won't read or mutate global or static data in their implementation.
There were a few functions in the C library which broke this rule, for example ctime.
ctime returns a pointer to static data and is not thread-safe.
The third is saying that a thread can mutate an element in a container while another thread accesses a different element.
This does not imply that it's also inherently safe to mutate the container, e.g. to insert a new element.
And vector<bool> is an exception to this rule:
std::vector<bool> behaves similarly to std::vector, but in order to be space efficient, it ... does not guarantee that different elements in the same container can be modified concurrently by different threads.
I am certain that, in practice, use of ::new is thread-safe. My question is what part of the standard provides that guarantee, if any? Is this a convention? Is this something where the standard gives implementations a lot of latitude (like the relatively loose constraints about what size each data type is) to support a wide variety of hardware?
I'm hoping that there's just a line in the C++11 standard somewhere that explicitly specifies "implementations of ::new must be thread-safe".
I'd also love to see some standardese about the thread-safety of operator new overloads. I imagine that they would also need to be required to be thread-safe, but these functions also do not fall under the blanket guarantee that const => thread safe (in C++11).
Thanks!
I believe this is implicitly guaranteed by the C++11 standard. If it were not, then usage of the operator new or new expression might cause a data race, and that would not be allowed by the standard. For reference, see §17.6.5.9 Data race avoidance and also
18.6.1.4 Data races [new.delete.dataraces]
"The library versions of operator new and operator delete, user replacement versions of global operator new and operator delete, and the C standard library functions calloc, malloc, realloc, and free shall not introduce data races (1.10) as a result of concurrent calls from different threads. Calls to these functions that allocate or deallocate a particular unit of storage shall occur in a single total order, and each such deallocation call shall happen before the next allocation (if any) in this order."
Your own overrides or your own replacements for the global operators should fulfill this requirement as well.
See also this proposal N3664 "Clarifying Memory Allocation", which puts more emphasis on that matter.
The C++ standard does not outright require that new be thread-safe. Some implementations explicitly support building C++ code in single-threaded mode, where the C standard library, including malloc() may not be thread-safe. The platforms most of us use every day do offer thread-safe allocation, of course.
Even if your platform provides a thread-safe new, you still need to be wary if you use any libraries which implement their own operator new, or if you do so yourself. It's certainly possible to write a new which works only in a single thread--maybe even intentionally!
I know it is not safe for malloc or free to be called, directly or indirectly, from a signal handler.
But, if I can guarantee that at least one shared reference will remain alive, is it then safe to copy-construct and destruct additional shared or weak references, or do I have to roll my own refcounting?
(Yes, I know signal handlers usually shouldn't do much. But I have a good reason this time.)
The C++ standard defines the concept of a "plain old function" as follows:
A POF (“plain old function”) is a function that uses only features from [the C/C++] common subset, and that does not directly or indirectly use any function that is not a POF, except that it may use plain lock-free atomic operations.
Furthermore:
The behavior of any function other than a POF used as a signal handler in a C++ program is implementation-defined.
Obviously, C++ class objects are not part of the C/C++ common subset, and therefore using them in a signal handler yields implementation-defined behavior.
Now, many implementations will allow you limited usage of C++ features. If your implementation doesn't permit memory allocations or exceptions, then here's what we know about those smart pointer types.
Then all weak_ptr constructors are explicitly declared noexcept. So they can't throw exceptions. This also means that they're not allowed to allocate memory (since failing to allocate memory would throw an exception). Yes, they could allocate memory and std::terminate if it fails, but that would be exceedingly rude for an implementation to do.
The copy&move constructors of shared_ptr similarly is noexcept, so the same applies. This is also true for the aliasing constructor for shared_ptr.
If you are absolutely certain that at least one shared_ptr will still exist, then destroying a shared_ptr is explicitly stated to not have side effects. Which probably includes memory de-allocations.
Those are the guarantees that the standard library gives you.
I am certain that, in practice, use of ::new is thread-safe. My question is what part of the standard provides that guarantee, if any? Is this a convention? Is this something where the standard gives implementations a lot of latitude (like the relatively loose constraints about what size each data type is) to support a wide variety of hardware?
I'm hoping that there's just a line in the C++11 standard somewhere that explicitly specifies "implementations of ::new must be thread-safe".
I'd also love to see some standardese about the thread-safety of operator new overloads. I imagine that they would also need to be required to be thread-safe, but these functions also do not fall under the blanket guarantee that const => thread safe (in C++11).
Thanks!
I believe this is implicitly guaranteed by the C++11 standard. If it were not, then usage of the operator new or new expression might cause a data race, and that would not be allowed by the standard. For reference, see §17.6.5.9 Data race avoidance and also
18.6.1.4 Data races [new.delete.dataraces]
"The library versions of operator new and operator delete, user replacement versions of global operator new and operator delete, and the C standard library functions calloc, malloc, realloc, and free shall not introduce data races (1.10) as a result of concurrent calls from different threads. Calls to these functions that allocate or deallocate a particular unit of storage shall occur in a single total order, and each such deallocation call shall happen before the next allocation (if any) in this order."
Your own overrides or your own replacements for the global operators should fulfill this requirement as well.
See also this proposal N3664 "Clarifying Memory Allocation", which puts more emphasis on that matter.
The C++ standard does not outright require that new be thread-safe. Some implementations explicitly support building C++ code in single-threaded mode, where the C standard library, including malloc() may not be thread-safe. The platforms most of us use every day do offer thread-safe allocation, of course.
Even if your platform provides a thread-safe new, you still need to be wary if you use any libraries which implement their own operator new, or if you do so yourself. It's certainly possible to write a new which works only in a single thread--maybe even intentionally!
Suppose I have:
stl::map<std::string, Foo> myMap;
is the following function thread safe?
myMap["xyz"] ?
I.e. I want to have this giant read-only map that is shared among many threads; but I don't know if even searching it is thread safe.
Everything is written to once first.
Then after that, multiple threads read from it.
I'm trying to avoid locks to make this as faast as possible. (yaya possible premature optimization I know)
C++11 requires that all member functions declared as const are thread-safe for multiple readers.
Calling myMap["xyz"] is not thread-safe, as std::map::operator[] isn't declared as const.
Calling myMap.at("xyz") is thread-safe though, as std::map::at is declared as const.
In theory no STL containers are threadsafe. In practice reading is safe if the container is not being concurrently modified. ie the standard makes no specifications about threads. The next version of the standard will and IIUC it will then guarantee safe readonly behaviour.
If you are really concerned, use a sorted array with binary search.
At least in Microsoft's implementation, reading from containers is thread-safe (reference).
However, std::map::operator[] can modify data and is not declared const. You should instead use std::map::find, which is const, to get a const_iterator and dereference it.
Theoretically, read-only data structures and functions do not require any locks for thread-safety. It is inherently thread-safe. There are no data races on concurrent memory reads. However, you must guarantee safe initializations by only a single thread.
As Max S. pointed out, mostly implementation of reading an element in map like myMap["xyz"] would have no write operations. If so, then it is safe. But, once again, you must guarantee there is no thread which modifies the structure except the initialization phase.
STL collections aren't threadsafe, but it's fairly simple to add thread safety to one.
Your best bet is create a threadsafe wrapper around the collection in question.
This is an old question, but as all existing answers -and in particular the accepted one- are incorrect, I am adding another one (sorry, not enough rep for comments).
Because the OP explicitly mentions a read-only access (_"giant read-only map"), the [] operator should actually not be used since it can result in an insert. The at or find methods are suitable alternatives.
Some non-const functions are considered const in the context of thread-safety. at and find are such pseudo-const functions (this is explained very nicely in another question. More details are available from cppreference.com as well under the "thread-safety" section. And finally and see C++11 standard 23.2.2.1 for the official list).
As long as only const operations are used on the container the execution is thread-safe. As you initialize the huge map once (and do not modify it afterwards) before accessing it read-only from other threads, you are safe!