5

The following test always produces failures or bus errors for me on my Intel Mac Mini.

Compiler:

uname -a:
Darwin vogon13 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386

g++ -v:
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5493~1/src/configure
--disable-checking -enable-werror --prefix=/usr --mandir=/share/man
--enable-languages=c,objc,c++,obj-c++
--program-transform-name=/^[cg][^.-]*$/s/$/-4.0/
--with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib
--build=i686-apple-darwin9 --with-arch=apple --with-tune=generic
--host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5493)

Compile commands:

"g++"  -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall
-pedantic -g -no-cpp-precomp -gdwarf-2 -Wno-long-double -Wno-long-long -fPIC
-DBOOST_DATE_TIME_NO_LIB=1 -DBOOST_THREAD_NO_LIB=1 -DBOOST_THREAD_USE_LIB=1
-DDATE_TIME_INLINE -DNDEBUG  -I"." -c -o "strtod_test.o" "strtod_test.cpp"

"g++"  -o "strtod_test" "strtod_test.o"
"libboost_thread-xgcc40-mt-s.a" "libboost_date_time-xgcc40-mt-s.a"
-g -nodefaultlibs -shared-libgcc -lstdc++-static -lgcc_eh -lgcc -lSystem
-Wl,-dead_strip -no_dead_strip_inits_and_terms -fPIC

Source code:

#include "boost/thread/thread.hpp"
#include "boost/thread/barrier.hpp"
#include <iostream>

using namespace std;

void testThreadSafetyWorker(boost::barrier* testBarrier)
{
    testBarrier->wait(); // wait until all threads have started
    try
    {
        const char* str = "1234.5678";
        const char* end = str;
        double result = strtod(str, const_cast<char**>(&end));
        if (fabs(result-1234.5678) > 1e-6)
            throw runtime_error("result is wrong!");
    }
    catch (exception& e) {cerr << "Exception in worker thread: " << e.what() << endl;}
    catch (...) {cerr << "Unhandled exception in worker thread." << endl;}
}

void testThreadSafety(const int& testThreadCount)
{
    boost::barrier testBarrier(testThreadCount);
    boost::thread_group testThreadGroup;
    for (int i=0; i < testThreadCount; ++i)
        testThreadGroup.add_thread(new boost::thread(&testThreadSafetyWorker, &testBarrier));
    testThreadGroup.join_all();
}

int main(int argc, char* argv[])
{
    try
    {
        testThreadSafety(2);
        testThreadSafety(4);
        testThreadSafety(8);
        testThreadSafety(16);
        return 0;
    }
    catch (exception& e) {cerr << e.what() << endl;}
    catch (...) {cerr << "Unhandled exception in main thread." << endl;}
    return 1;
}

Stack trace:

(gdb) bt
#0 0x950c8f30 in strlen ()
#1 0x9518c16d in strtod_l$UNIX2003 ()
#2 0x9518d2e0 in strtod$UNIX2003 ()
#3 0x00001950 in testThreadSafetyWorker (testBarrier=0xbffff850) at strtod_test.cpp:21
#4 0x0000545d in thread_proxy (param=0xffffffff) at boost_1_43_0/libs/thread/src/pthread/thread.cpp:12? ?1
#5 0x950f1155 in _pthread_start () #6 0x950f1012 in thread_start ()
Matt Chambers
  • 2,229
  • 1
  • 25
  • 43
  • 1
    Your code works fine for me (once I add a closing brace to the line `catch (exception& e) {...` in `testThreadSafetyWorker`). What does the backtrace look like in gdb when you get a failure/bus error? – Adam Rosenfield Jun 29 '11 at 21:41
  • @Adam Rosenfield: Thanks for the note on the missed brace, I fixed it. I assume you're testing on some version of Darwin: which one? If not, I know other platforms work fine, both POSIX and MSVC. See updated question for stack trace. – Matt Chambers Jun 29 '11 at 22:10
  • GCC 4.0.1 dates back to July 7, 2005. That's coming up on _seven years ago_. Just FYI. – Lightness Races in Orbit Jun 29 '11 at 22:16
  • I don't think `strtod` is supposed to be thread-safe. Some implementations explicitly claim thread-safety for it (e.g. MKS Toolkit), but I can find no evidence of that for glibc. Why do you think that it is? – Lightness Races in Orbit Jun 29 '11 at 22:19
  • 4
    POSIX requires any function not explicitly documented as non-thread-safe to be thread-safe. See IEEE Std 1003.1-2008, XSH 2.9.1. – R.. GitHub STOP HELPING ICE Dec 11 '11 at 07:04

1 Answers1

2

strtod is not thread safe. It's never been stated in any specs that it was thread-safe. You should be using strtod_l, which takes a locale. In all likelehood there is a shared data structure that is being clobbered by it's use in the threaded code.

locale_t c_locale = newlocale (LC_ALL_MASK, "C", 0);
r = strtod_l(nptr, endptr, c_locale);
freelocale (c_locale);
Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • 1
    I was actually led to this error by using lexical_cast, which in turn uses C++ streams, which in turn uses strtod (at least on Darwin). So evidently I'm not the only one who thinks it should be thread safe! I know it's not guaranteed to be thread safe, but why on earth would it not be. :( Creating a locale for every call? Isn't lexical_cast slow enough already? ;) So what does someone using lexical_cast or C++ streams do in this case? – Matt Chambers Jun 29 '11 at 22:25
  • @Matt Chambers: use a modern compiler? `strtod` is part of the compiler. – MSalters Jun 30 '11 at 08:12
  • strtod is part of glibc, IIRC. There was a fix for the library some time ago to ensure that it was thread safe. Maybe that is not in the Darwin copy of the library. – Anya Shenanigans Jun 30 '11 at 09:21
  • @MSalters, @Tomalak Geret'kal: I upgraded to llvm-gcc42 and the problem persists. That's the latest Darwin compiler provided by MacPorts. I'll try vanilla GCC 4.4 next. Funny how Microsoft's unmodern compilers got this right but not Apple's... – Matt Chambers Jun 30 '11 at 15:05
  • 3
    `strtod` is thread-safe, or at least it's required to be thread-safe by posix. If it's not thread-safe on Darwin, that's (yet another) conformance bug in a broken OS... – R.. GitHub STOP HELPING ICE Dec 11 '11 at 07:01
  • strtod is marked as "MT-Safe locale" in glibc 2.22. – Changming Sun Feb 22 '16 at 15:44