James said:
Everything in the standard library in g++, from 3.0 on, is
supposed to be thread safe.
For some meanings of thread safe.
None of the stl classes as far as I know support simultaneous
modification from multiple threads.
... There is some uncertainty, for some
classes, as to how this is defined, and I hesitated to post the
bug, because I wasn't sure that std::string was supposed to meet
the Posix requirements (although all of the other g++ containers
meet them). That is, however, a bit irrelevant to the
discussion here. Posix compliant thread safety is a reasonable
choice, the current implementation of std::string in g++ doesn't
meet it, and I cannot imagine any possible test which would
display this defect in a reliable fashion.
See attached: It finds the "supposed" bug in 24 milliseconds on my machine.
That definitly should (and as far as I know, does) work. The
problem is more along the lines of:
std::string global( "a" ) ;
void thread1()
{
std::string s1( global ) ;
}
void thread2()
{
std::string s2( global.begin(), global.end() ) ;
}
Since I'm not modifying global ...
global.begin() is pulling a modifyable pointer - all bets are off. It's
a non-const call so the implementation is free to do whatever it wants
to global.
In my initial implementation of the test I had taken a const reference
to the string (as a default thing I do without thinking) before I called
begin. Then I proceeded to tweak the parameters to trigger the problem,
I has one test run for ten minutes and alas, NO failures. So, I looked
again and I made it non-const and viola immediate failure.
I don't think I would call this one a bug.
....
The issue hasn't been raised to date, but...
Good code is easy to understand. Code that isn't easy to
understand fails code review. How does testing verify this?
(In some ways, easy to understand is more important than an
absence of errors.)
Ya ... I thought we were talking about UNIT TESTING ! Do you like to
digress all the time ?
That's doing things the hard way, and is hardly cost effective.
Having the computer do the hard work is far better than for me doing the
hard work. My B.S. meter just pegged again.
In code review, you are told that you forgot to initialize
variable i in line 1234 of abc.cc. The unit test tells you that
tests 25 through 30 all fail. Which information makes it easier
to find and fix the error?
And a well run code review doesn't find just the obvious bugs.
It also finds those which no test will find. (I found the bug
in the g++ implementation of std::string by reviewing the code.)
I don't consider this one a bug. It's expected to fail IMHO.
....
Read the "should"... Most inferences are maintained when the underlying
layers are changed.
/**
* Test std::string.
*
*/
#include "at_thread.h"
#include "at_atomic.h"
#include "at_lifetime_mt.h"
#include "at_unit_test.h"
#include <string>
namespace {
AT_TestArea( STDString, "std::string test" );
// ======== STDStringTest =============================================
/**
* STDStringTest is a test object. All the calls to test1...testN
* should be thread safe.
*
*/
class STDStringTest
: public at:

trTarget_MT
{
public:
// Should this be const or not - I think it should
// James Kanze possibly believes otherwise.
// comment out the KANZE if you think that calling begin()
// on a non-const std::sting should be thread safe !
#define KANZE
#ifndef KANZE
typedef const std::string t_local_type;
typedef std::string::const_iterator t_iter_type;
#else
typedef std::string t_local_type;
typedef std::string::iterator t_iter_type;
#endif
/**
* STDStringTest
*
*/
STDStringTest()
{
m_strings[ 0 ].append( s_length, char( 0 + 'A' ) );
for ( unsigned i = 1; i < s_count; ++ i )
{
m_strings[ i ] = m_strings[ 0 ];
}
}
enum { s_count = 43 }; // prime number
static const unsigned s_length = 3; // tweak for maximum effect
std::string m_strings[ s_count ];
virtual void test2( int l_val, std::string & o_tval )
{
t_local_type & l_str = m_strings[ l_val ];
t_iter_type beg = l_str.begin();
// at::OSTraitsBase::SchedulerYield();
o_tval.assign( beg, l_str.end() );
AT_TCAssert( o_tval == l_str, "Copy of string failed !" );
}
virtual void test1( int l_val, std::string & o_tval )
{
t_local_type & l_str = m_strings[ l_val ];
o_tval = std::string( l_str );
AT_TCAssert( o_tval == l_str, "Copy of string failed !" );
}
virtual void test3( int l_val, std::string & o_tval )
{
o_tval += "X";
}
virtual void test4( int l_val, std::string & o_tval )
{
o_tval = "";
}
};
at:

tr<STDStringTest *> g_test = new STDStringTest();
// ======== String_TaskBase =============================================
/**
* defines a class for co-operating threads.
*/
template <int N, int ThreadCount = 3 >
class String_TaskBase
: public at::Task
{
public:
virtual ~String_TaskBase() {}
static const int m_thr_count = ThreadCount;
static const int m_iters = 1 << 20;
static volatile unsigned m_count;
static volatile unsigned m_count_left;
static at::AtomicCount m_value;
static at::ConditionalMutex m_mutex;
virtual void TestWork( int l_thrnum ) = 0;
static at:

tr< at::MutexRefCount * > s_mutex;
static at:

tr< at::MutexRefCount * > s_assmutex;
void Work();
};
template <int N, int ThreadCount >
at::AtomicCount String_TaskBase<N,ThreadCount>::m_value;
template <int N, int ThreadCount >
at::ConditionalMutex String_TaskBase<N,ThreadCount>::m_mutex;
template <int N, int ThreadCount >
at:

tr< at::MutexRefCount * > String_TaskBase<N,ThreadCount>::s_mutex;
template <int N, int ThreadCount >
at:

tr< at::MutexRefCount * > String_TaskBase<N,ThreadCount>::s_assmutex;
template <int N, int ThreadCount >
volatile unsigned String_TaskBase<N,ThreadCount>::m_count;
template <int N, int ThreadCount >
volatile unsigned String_TaskBase<N,ThreadCount>::m_count_left;
template <int N, int ThreadCount >
void String_TaskBase<N,ThreadCount>::Work()
{
unsigned l_num;
{
// stuff is done here
at::Lock<at::ConditionalMutex> l_lock( m_mutex );
l_num = m_count ++;
++ m_count_left;
if ( ! s_mutex )
{
s_mutex = new at::MutexRefCount( at::Mutex::Recursive );
s_assmutex = new at::MutexRefCount( );
}
if ( ( m_thr_count - 1 ) == l_num )
{
std::cerr << l_num << " calling PostAll\n";
l_lock.PostAll();
}
else
{
l_lock.Wait();
}
}
TestWork( l_num );
}
const unsigned g_primes[] = { 1, 3, 7, 11, 13 };
class HardTask
: public String_TaskBase< 2 >
{
public:
static at::AtomicCount s_task_counter;
static int s_xcount;
// each test thread calls this with a unique thread number
virtual void TestWork( int l_thrnum )
{
unsigned pnum = g_primes[ l_thrnum % at::CountElements( g_primes ) ];
unsigned count = l_thrnum;
unsigned done = 0;
std::string l_teststr;
for ( int i = 0; i < m_iters; ++i )
{
int choice = ( i * pnum ) % g_test->s_count;
switch ( l_thrnum & 1 ? i % 8 : (7 - (i % 8) ) )
{
case 0 : case 2 :
g_test->test1( choice, l_teststr );
break;
case 1 : case 3 :
g_test->test2( choice, l_teststr );
break;
case 4 :
g_test->test3( choice, l_teststr );
break;
case 5 :
g_test->test4( choice, l_teststr );
break;
}
// after every 1<<5 iterations - rebuild the test object
// with brand new strings
if ( true && !( i % (1<<5) ) )
{
// stuff is done here
at::Lock<at::ConditionalMutex> l_lock( m_mutex );
int l_num = ++ s_xcount;
if ( m_thr_count == l_num )
{
s_xcount = 0;
g_test = new STDStringTest();
l_lock.PostAll();
}
else
{
l_lock.Wait();
}
}
}
s_task_counter.Bump( done );
}
HardTask()
{
Start();
}
~HardTask()
{
Wait();
}
};
int HardTask::s_xcount;
at::AtomicCount HardTask::s_task_counter;
AT_DefineTest( HardStdString, STDString, "multithreaded std::string test" )
{
void Run()
{
{
// This will start all the threads
HardTask l_tasks[ HardTask::m_thr_count ];
// and end the test threads
}
}
};
AT_RegisterTest( HardStdString, STDString );
} // namespace