Pete Becker wrote:
Morpheus wrote:
....
>Any articles on this subject are welcome as well.
Thread-related proposals under discussion for the next C++ standard:
Concurrency memory model
http://www.open-std.org/jtc1/sc22/wg...2006/n2052.htm
Thread-local storage
http://www.open-std.org/jtc1/sc22/wg...006/n1966.html
Atomic operations
http://www.open-std.org/jtc1/sc22/wg...006/n2047.html
Thread support
http://www.open-std.org/jtc1/sc22/wg...005/n1907.html
From the article:
.... Since the thread cannot then unlock the mutex, the result is a deadlock.
I have seen models where thread T1 can aquire a non-recursive mutex and
T2 can release the mutex. In some cases I have used that kind of
"transfer of aquisition of mutex" in real code. Sounds like the
"deadlock" semantics are nonsensical if there is a viable alternative.
http://www.open-std.org/jtc1/sc22/wg...006/n2090.html
I think also that the standard should be more specific about the use of
function statics. i.e.
void function()
{
static T t = expression;
}
If "function" is called simultaneously by 2 threads, expression should
be evaluated exactly once and if the initialization of t is being done
by "thread 1" then "thread 2" should wait until the initialization is
complete.
This is currently implied by the standard that says the initialization
of t must occur once but that requirement should be clarified in the
event of the standard becoming "thread aware". Plenty of discussion
around this "bug" can be found in the gcc bug list. I believe that
since gcc 4.0, it guarentees this for threaded code. I don't recall any
other compiler (besides gcc) guarenteeing this yet.
For gcc 4.0 and above, this is already a "call_once" construct with no
additional specification in the standard.
This in essence is a "once" function and eliminates the need for more
complexity in the "call_once" mechanism.
TSS is a very contentious issue since it seems to be used in different
ways by different people (experts) and hence raises all sorts of
conflicting requirements. TSS is already supported in gcc by the
__thread keyword as a storage specifier. I think this is the most
powerful model. This is the rationale. In the very few cases where I
have needed to use TSS, it has usually been where code is either legacy
or the application is for high performance. In the case of legacy code,
there is a variable (e.g. errno) which is being read/written to by
multiple threads simultaneously. In this case, converting the code to
be thread safe is simply a matter of declaring the variable "thread
specific" at which point the address of the variable is the same across
all threads but the variable is placed in a thread private page. This
is a very simple, reliable and "tested" model. The other case where TSS
is used when performance is an issue. It is very hard to argue that
from a performance perspective, depending on hardware support is the
solution of least overhead, also in almost all modern systems this type
of VM support is available. The con argument is that threads will have
objects that are not visible to one another and this is "bad", the
counter argument is that if this is not bad since if visibility is
required, you can still provide visibility like this:
T objects[MAXTHREADS];
__thread int my_thread_number = get_next_consecutive_thread_number();
__thread T & myobj = objects[ my_thread_number ];
This brings up a whole "thread segment" initialization and destruction
question for TSS. Nonetheless, this is something that is not too
unreasonable. If this is an issue, you could eliminate the question by
making a requirement that __thread objects are POD with no initializers
or non POD's TSS objects are allowed in function static variables only.
The intent of the code above still stands since this can be done in a
application specific thread initialization function.
This there is also the "thread once" concept which is like the
"call_once" concept raised, but it happens once per thread. This can be
achieved by this construct:
void function()
{
__thread static T t = expression;
}
Here, the "t" initializer is called once per thread.
The use of TSS in practice is very limited. In the past I have used it
to create very fast thread primitives and in the case where from an API
perspective I needed an application context in a high performance legacy
graphics application that was needed a thread specific context to
implement a "multi threaded" rendering architecture. Hence, use of TSS
should only be needed in very very few cases but where it is needed it
is probably accompanied by the need for tight performance as well.
On the issue of condition variable "spurious wakeup". Someone please
explain to me why the condition variable implementation can't fix this
problem. In all the thousands of condition variable tests I have done,
I don't think I have ever detected a "spurious" wakeup. This should
just be "fixed" as complicating the usage for programmers is just asking
for trouble. Also, as part of the win32 condition variable
implementations I have seen, I don't think it was possible to have a
"spurious" wake up. I suggest that it is not acceptable to have
"spurious" wake up as part of the standard. That still does not mean
the suggestion in the article that the condition should not be check is
a good one, but that should be an application specific issue.
....
There should be a "convenience class" that is a mutex and condition
variable i.e.:
class condition_mutex
: public mutex,
public condition
{ ... };
90 % of the cases, this is what is used and makes the API straight
forward for most cases. This then begs the question of "why all these
mutex types" ? (i.e. mutex, tr_mutex, timed_mutex, recursive_mutex,
etc). In the Austria API, there is one mutex type that can be tweaked
at initialization.
....
The "islock()" method is asking for trouble. In a threaded environment,
this is next to useless since by the time "islock" returns, it is quite
possible that the return value does not reflect the nature of the lock,
this should be replaced with a "trylock".
If I understand the mutex api, it appears that I can't "try lock" on a
regular mutex ? I don't think there is any practical reason to do this.
A regular mutex should have a "trylock" method.
....
thread::cancel is notoriously difficult to use properly. I would
suggest to avoid the whole "cancel" thread idea. I would say it is the
application's responsibility to provide a safe way to "terminate early"
as only the application is able to tell if it has left a consistent
state behind if it leaves.
Managing "thread lifetime" and "object lifetime" is critical, the rule
should be that the lifetime of the thread cannot be longer than the
lifetime of the threads "primary" object. By looking at the "create"
API I can't tell if this can be enforced. My experience with the
Austria C++ "Task" API (which incidentally is concept is similar to the
ACE task api), is that combining of the "join on destruct" forces a
paradigm than minimizes programmer errors yet does not relinquish any
"power of the API".
....
There is a craete() API implied race condition between thread creation
and the thread handle being "known". In the case of the at::Task API,
this race condition is impossible because by the time the thread starts
executing application code, the handle is known. In the case of the
proposed thread API, the thread handle is not known until the thread has
already potentially run amok.
Which brings me to the rule: Asynchronous apis cannot return values
reliably. Or said in a practical way, "Never return a value from an
asynchronous API".
....
These comments are just from a cursory look at the boost API. I think
there are some serious deficiencies in this proposed thread API and I'd
like to see some more analysis before it gets committed to the standard.
/g