Last Modification: December 11, 1999

  Is the STL included with VC++ thread-safe?

  The answer is slightly complex. Concurrent reads from multiple threads shouldn't cause  problems, unless at least one thread tries to write to the container. Here the problem is that the STL itself won't synchronize access, and you have to be careful about any iterators that might get invalidated. For example, if one thread tries to delete one element of a vector, you could invalidate several iterators. For this reason, it's always a good idea to protect multiple-thread access to STL containers. Many problems in this category could benefit from a Single Writer-Multiple Reader synchronization approach.

However, you should install the updates provided by Dinkumware (from which MS licenses the STL) at http://www.dinkumware.com/vc_fixes.html

On the other hand, things with the rest of the Standard Library aren't so simple.  Take for example basic_string, then as Dr. Plauger says on that page, simultaneous reads and writes aren't thread-safe. The reference counting basic_string uses isn't thread-safe at all, because it doesn't update the reference count atomically. Worse, simultaneous reads aren't thread-safe, if the string rep is shared. For example:


    // Initialization is assumed completed before the thread functions begin
    string s = "abc";
    const string& q = s;

    bool thread1()
    {
       // Could also be s.find() and other member functions in
       // typical implementations, because they don't unshare
       // or mark the rep unshareable
       return q[0] == q[1];
    }

    bool thread2()
    {
       string t = s;
       return s[0] == s[1];
    }

The problem is that q[n] returns a string::const_reference and does not cause unsharing of the rep, while s[n] produces a string::reference, which causes unsharing. If evaluation of q[n] in thread1 gets to the rep+n point, leaving a pointer into s's rep (srep), and thread2 interrupts, shares srep by copying s to t, and executes s[n], srep is unshared, and thread1 is left with a pointer into what is now  trep. In other words, t now owns the rep in use by thread1. And if t goes out of scope before thread1 is finished, thread1 is left with a dangling pointer, which it has yet to dereference. It's also bad if thread2 modifies t, following the unsharing of srep, while thread1 has a pointer into trep; that doesn't constitute writing to s, so you would think it should be OK, but it isn't.

Note that in this sort of implementation, besides unsharing the rep, production of a non-const iterator or reference must also mark it as unshareable; it can be made shareable again when you call a member function that is documented to invalidate iterators and references. Defining q in the example above as a const string& ensures that q[n] doesn't mark srep as unshareable, setting up the problem in the concurrent execution of thread1() and thread2(). (Marking the rep as unshareable precludes sharing it between the time it's marked unshareable and the time iterators and references into it are invalidated. This allows you to modify the string through an iterator or reference without worrying about the string having become shared again, in which case, you could end up modifying multiple strings.)

What's going on in thread2() could be called "copy-just-in-case", because the rep is unshared even though it isn't being written to. This wouldn't happen in a true copy-on-write implementation, which, unfortunately, the C++ Standard effectively forbids, because it requires string::reference to be a real C++ reference. If it could be a reference proxy class, then you could get true copy-on-write, and the problem described above couldn't occur. However, things like the following would be illegal:


    template<class T> void fun(T,T);
    string x = "abc";
    fun(x[0],'z');

x[0] would return the proxy class, and even though it has an operator char(), template argument deduction doesn't consider it. However, you can use a cast to get around it:


    fun(static_cast<char>(x[0]),'z');

This is kind of ugly, though, but in practice, it isn't much of a problem, as it doesn't come up that often.

References  and Samples: