STL & multithreading

I

Ioannis Vranos

Axter said:
I've tested it with 5 threads trying to access it at the same time, and
it works great.
However, I have not tested it for performance.


Generally speaking, with any wrapper class aside, many times multithreading applications
run with no problem in single-processor systems and show up their problems in systems with
more than one processor.
 
I

Ioannis Vranos

Ioannis said:
With wrapper classes aside, do you think that there are not
multithreading-enabled STL implementations? I am not sure what you mean
with the redundant part too.

But first let's define what we mean with "thread-safety". I assume we
are talking about different threads accessing safely different parts of
a container (like a vector)
or

calling a given function concurrently,
right?
 
P

Pete Becker

Ioannis said:
With wrapper classes aside, do you think that there are not
multithreading-enabled STL implementations? I am not sure what you mean
with the redundant part too.

But first let's define what we mean with "thread-safety". I assume we
are talking about different threads accessing safely different parts of
a container (like a vector) and calling a given function concurrently,
right?

For the third time: to write a thread-safe application you need an
application-level locking policy and application-level locks. Using
thread-safe containers does not make your application thread-safe, and
application-level locks usually make container-level locks redundant.
See the example that I gave earlier.
 
A

Axter

Pete said:
For the third time: to write a thread-safe application you need an
application-level locking policy and application-level locks. Using
thread-safe containers does not make your application thread-safe, and
application-level locks usually make container-level locks redundant.
See the example that I gave earlier.

Saying it a third time doesn't make it true.
Container locks do work.
If you have a container lock, then that makes the application lock
redundant.
Using an application lock is more of a procedural based logic
mentality.
This is fine for programs like C, but a more OO style language should
lean to a more Object Orientated approach.

By setting up the code so the object itself has the lock, you make the
code more OO, and safer then if you used the application level lock
approach.
 
P

Pete Becker

Axter said:
Container locks do work.
If you have a container lock, then that makes the application lock
redundant.
Using an application lock is more of a procedural based logic
mentality.

Sigh. I gave you an example that doesn't work with container-level
locks, and an explanation of why it doesn't work. Until you respond to
that example and that explanation there is no point in any further
discussion.
 
A

Axter

Andre said:
Yes, he was talking about using your wrapper class. It was simply not
shown in the code example for brevity's sake. So to rephrase:

while (!vec.empty())
vec.pop_back();

Replace "vec." with "obj->GetLockedObject()->". So let's assume we start
with a 1000000 element vector, and we launch 100 threads with the above
code in it to clear out the vector (and for some strange reason we don't
want to just .clear() it...).

A thread safe object is not going to stop you from performing bad logic
in multiple threads.
A thread safe object's main job is to only allow ONE thread to access
it any given time. That's it.....
Let's continue the example that say we're down to the last element, and
25 of the threads check vec.empty() and then there's a thread context
switch right after that. The other 75 threads may be just after the
vec.pop_back(). One of the 75 gets the thread context, checks for
vec.empty() and then performs the vec.pop_back(), then context switch.
Say the remaining 74 threads are the ones that get the next contexts.
They all test vec.empty(), find the vector empty, and proceed along their
codepath. Finally those first 25 that were sitting just after the
vec.empty() call get their timeslices. All 25 will attempt a pop_back on
an empty vector. Boom. (OK, Undefined Behaviour)
If a thread needs to perform an operation that requires the state to be
stable between multiple access to the object, then using the thread
safe wrapper you can create a RefLock object. This will allow the
object to be locked while you perform multiple operations with it.
Example:
ThreadSafeObject<vector<int> >::RefLock MyLockedVec =
MyRefVectorInstance.GetLockedObject();
if (MyLockedVec->size() > 10000 && MyLockedVec->size()&1) //Due odd
numbers
{
MyLockedVec->pop_back();
}

Translation, your wrapper class does _not_ provide thread safety as a
silver bullet. It _only_ provides thread safety in a single atomic
operation. As soon as you need multiple operations on the object to
perform a task, you're implementation risks a context switch between the
two operations where the state of the object may be modified.

That's incorrect. If you have a better understanding of the wrapper
class, you'll see that you can do the above logic in a thread safe
manner with the wrapper class protecting the object from being access
simultaneously by multiple threads.
 
I

Ioannis Vranos

Pete said:
For the third time: to write a thread-safe application you need an
application-level locking policy and application-level locks.


Yes, this on the application-level logic (and in addition to other multithreading
mechanisms that do not involve locks).

Using
thread-safe containers does not make your application thread-safe, and
application-level locks usually make container-level locks redundant.
See the example that I gave earlier.


Yes in the application-level logic having a container like vector thread-safe does not
make thread-safe the access of the same container element by two or more threads
simultaneously.


My experience is somewhat limited, however what I had in mind as a thread-safe vector for
example, is a vector that accessing or modifying one element does not modify the others.


A practical example I can give on this is construct-level multithreading, with OpenMP
standard supported by many C++ compilers (and even more in the future). VC++ 2005 will
also support this.


#include <vector>


int main()
{
using namespace std;

vector<int> vec(100);

// ...

#pragma omp for
for(vector<int>::size_type i=0; i<vec.size(); ++i)
vec= i;
}



With this OpenMP #pragma directive, you provide the guarantee that each element
access/modification is independent of the others, so the compiler generates more than 1
threads for the assignments. In this code for example, when using one of the upcoming
multi-core AMD/Intel processors, or an existing multi-processor system, the assignments
will take advantage of the two or more processors/cores and finish in less time than in
the usual single thread case.

OpenMP multithreading is independent from other application-level multithreading
mechanisms provided in a system. Also in a compiler not supporting OpenMP, as the standard
mentions, unknown #pragmas are ignored, so the code remains portable.


In a container like this, what I consider as thread safe is a container that would not
interfere a specific element access to another element.
 
I

Ioannis Vranos

Axter said:
Saying it a third time doesn't make it true.
Container locks do work.
If you have a container lock, then that makes the application lock
redundant.


I am not sure I can understand you in this. Yes, one container may acquire the lock when
writing assigning a value to one of its elements for example, however when writing other
application parts, for example network connections (or a simpler example, games), one has
to use application-level multithreading either to avoid application freezing in the first
case or to take advantage of multiprocessor systems in both cases.

Using an application lock is more of a procedural based logic
mentality.
This is fine for programs like C, but a more OO style language should
lean to a more Object Orientated approach.

By setting up the code so the object itself has the lock, you make the
code more OO, and safer then if you used the application level lock
approach.


I have experience in .NET multithreading which is purely OO, yes a good practice is to
make every component multithreading from the bottom up, but this involves more than
containers, it is about thread-lock in the application level, unless you do not consider
objects as part of the application-level, in which case it would be better to clarify what
exactly do you consider this, and what you consider as application-level multithreading.
 
I

Ioannis Vranos

Ioannis said:
Yes in the application-level logic having a container like vector
thread-safe does not make thread-safe the access of the same container
element by two or more threads simultaneously.


Actually it can make this access thread-safe if the container uses locks. So you may
forget this paragraph.
 
P

Pete Becker

Ioannis said:
My experience is somewhat limited, however what I had in mind as a
thread-safe vector for example, is a vector that accessing or modifying
one element does not modify the others.

Yes, certainly. Axter's assertion was that STL containers are not
thread-safe, and that adding his wrapper makes them thread-safe. Under
this criterion, he's wrong about every implementation of STL that I know
of and he's wrong about what his wrapper does.
 
U

Uenal Mutlu

Generally speaking, with any wrapper class aside, many times multithreading applications
run with no problem in single-processor systems and show up their problems in systems with
more than one processor.

What's the reason?
 
P

Pete Becker

Uenal said:
What's the reason?

Timing. On a single processor system one thread runs for a while then
gets pre-empted and another thread starts running. So conflicts between
threads will only show up as a result of the pre-emption. On a
multi-processor system multiple threads run at the same time, so
conflicts between threads can arise at any time.
 
A

Axter

Andre said:
Yes, he was talking about using your wrapper class. It was simply not
shown in the code example for brevity's sake. So to rephrase:

while (!vec.empty())
vec.pop_back();

Replace "vec." with "obj->GetLockedObject()->". So let's assume we start
with a 1000000 element vector, and we launch 100 threads with the above
code in it to clear out the vector (and for some strange reason we don't
want to just .clear() it...).

Let's continue the example that say we're down to the last element, and
25 of the threads check vec.empty() and then there's a thread context
switch right after that. The other 75 threads may be just after the
vec.pop_back(). One of the 75 gets the thread context, checks for
vec.empty() and then performs the vec.pop_back(), then context switch.
Say the remaining 74 threads are the ones that get the next contexts.
They all test vec.empty(), find the vector empty, and proceed along their
codepath. Finally those first 25 that were sitting just after the
vec.empty() call get their timeslices. All 25 will attempt a pop_back on
an empty vector. Boom. (OK, Undefined Behaviour)

Translation, your wrapper class does _not_ provide thread safety as a
silver bullet. It _only_ provides thread safety in a single atomic
operation. As soon as you need multiple operations on the object to
perform a task, you're implementation risks a context switch between the
two operations where the state of the object may be modified.

That's incorrect. If you have a better understanding of the wrapper
class, you'll see that you can do the above logic in a thread safe
manner with the wrapper class protecting the object from being access
simultaneously by multiple threads.

If you have to perform several functions who's results depend on each
other, then you can lock access to the object by using a RefLock
variable.

For the duration of the RefLock variable, the object is locked and only
the one thread can access it.
You can then safely do multiple functions who's results depend on each
other, and do it in a thread safe way.
example:

ThreadSafeObject<std::vector<int> >::RefLock MyLockedVec =
MyRefThreadSafeOjbectTestCase.MyThreadSafeVectorInstance.GetLockedObject();
if (MyLockedVec->size() > 10000 && MyLockedVec->size()&1) //Due odd
numbers
{
MyLockedVec->pop_back();

For a more complex example see following Unit Test class that is able
to perform all above senerios in an thread safe way.
http://code.axter.com/ThreadSafeOjbectTestCase.h
http://code.axter.com/ThreadSafeOjbectTestCase.cpp

The above unit test has 13 threads accessing the same object, and using
the ThreadSafeObject class to allow the objects to be access in a
thread safe manner.

This OO approach is safer, and it makes application locks redundant.
 
A

Axter

Pete said:
Sigh. I gave you an example that doesn't work with container-level
locks, and an explanation of why it doesn't work. Until you respond to
that example and that explanation there is no point in any further
discussion.

Sigh.
Your examples did not use the wrapper class.
And even if it did, the wrapper class can handle logic in which you
need to perform multiple access to the object in a thread safe manner.
All you need to do is create a RefLock object that last the scope of
the reqired lock,
For an example, see following link:

http://code.axter.com/ThreadSafeOjbectTestCase.h
http://code.axter.com/ThreadSafeOjbectTestCase.cpp

http://code.axter.com/ThreadSafeObject.h

The above Unit Test class proves that the ThreadSafeObject wrapper
class works.
If you disagree, please post proven tested code, that will show it to
break, and not unproven theories.
 
A

Axter

Pete said:
Yes, certainly. Axter's assertion was that STL containers are not
thread-safe, and that adding his wrapper makes them thread-safe. Under
this criterion, he's wrong about every implementation of STL that I know
of and he's wrong about what his wrapper does.

Please don't put words into my mouth.
That is not my assertion.
I believe there are some STL implementations that are thread safe, and
some that are not.
My wrapper class can not make an STL class thread safe.
It can make an instance of the STL class thread safe.

It protects the instance of the STL class from being access
simultaneously by multiple threads.
It can not stop the class from trying to access shared objects.
Will continue this in a later thread, with examples.
 
I

Ioannis Vranos

Axter said:
I believe there are some STL implementations that are thread safe, and
some that are not.
My wrapper class can not make an STL class thread safe.
It can make an instance of the STL class thread safe.


In general, don't most implementations that support multithreading provide a thread-safe
standard library?

E.g. VC++:

http://msdn.microsoft.com/library/d.../html/vclrfThreadSafetyInStandardCLibrary.asp


So I suppose in most cases such a wrapper isn't much useful. Also this wrapper implies
overhead when used with a thread-safe standard library, in comparison to using the
thread-safe standard library itself.

For example, #pragma omp for of OpenMP standard can't prove useful when used with a
wrapped container.

The only value I can think of such a wrapper class is for multithreading-enabled compilers
that do not provide any multithreading-enabled (=thread safe) standard library. Are there
such cases?
 
A

Axter

Ioannis said:
In general, don't most implementations that support multithreading provide a thread-safe
standard library?
E.g. VC++:

When you use a thread-safe standard library like VC++, that means the
class itself is thread safe.
That doesn't not mean that an instance of the class is going to be
thread safe.
My wrapper class makes the instance thread safe. It will not, and can
not make the class itself thread safe.
Example:

class I_AM_A_ThreadsafeClass
{
public:
I_AM_A_ThreadsafeClass(int x):m_x(x){}
I_AM_A_ThreadsafeClass(const
I_AM_A_ThreadsafeClass&src):m_x(src.m_x){}
I_AM_A_ThreadsafeClass& operator=(const I_AM_A_ThreadsafeClass& src)
{
if (&src != this)
{
m_x = src.m_x;
}
return *this;
}
int ThreadSafeFunction(){return m_x;}
private:
int m_x;
};
class Not_A_ThreadsafeClass
{
public:
Not_A_ThreadsafeClass(int x):m_x(NotAThreadSafeFunction(x)){}
Not_A_ThreadsafeClass(const
Not_A_ThreadsafeClass&src):m_x(NotAThreadSafeFunction(src.m_x)){}
Not_A_ThreadsafeClass& operator=(const Not_A_ThreadsafeClass& src)
{
if (&src != this)
{
m_x = NotAThreadSafeFunction(src.m_x);
}
return *this;
}
int NotAThreadSafeFunction(int x)
{
static int NotTrheadSafe = 0;
NotTrheadSafe +=x;
return NotTrheadSafe;
}
private:
int m_x;
};
 
I

Ioannis Vranos

Axter said:
When you use a thread-safe standard library like VC++, that means the
class itself is thread safe.
That doesn't not mean that an instance of the class is going to be
thread safe.


May you explain what you mean with the above, using std::vector as an example?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,202
Messages
2,571,057
Members
47,666
Latest member
selsetu

Latest Threads

Top