Thread: Defining "thread-safety" and maybe something that isn't "thread-safety"

  1. #16
    Registered User
    Join Date
    May 2007
    Posts
    147
    This thread developed quickly - everyone writes like twitter it seems (I'm just an old man complaining about 4 or 5 posts that appeared while I was writing this )

    If you have a library that isn't itself threadsafe, and you must use it, you can't use it with threads, you must resort to processes and IPC if required, or replace the library.



    I don't know if I'm using "thread-safe" here properly, but I've definitely hit upon a problem of concurrency that deserves to have a name. So what is it?
    I've been writing threaded applications for years (OS/2 and Unix when it finally supported it - Unix was all about processes in it's earlier versions, not threads).

    In all that time I've seen the lexicon change, and I'm not convinced it is as well defined as other regions of this science.

    Concurrency does have a more reasonable definition; two or more things happening at once, but it doesn't really imply safety. In fact, it is an error to invoke concurrency on a common resource without some form of protection - that doesn't stop the concurrency, it's just a bug, like you've observed.

    The fact is that the C library was designed at a time when Unix (the OS which emerged from C, as I read that history) did NOT have threads. It had processes, but not threads within a process - at least not in the 'standard' distributions I had seen. The library assumed 'ownership' such that functions like localtime and asctime used a static structure - obviously a design assumption that may have been fine at the time, but an example of lack of foresight (but that was, what, 40 years ago).

    Indeed, you must carefully select API's in threaded development because of this, and I submit one should prefer to avoid C library use as much as is practicable if you intend to use threads. That doesn't mean not to use it at all, but to employ a simple C++ axiom - encapsulate so you can control it.

    In your example, foo and goo CAN (though likely won't) call asctime at the same moment. Obviously this could be a problem in some contexts, but since they're being called at the same moment, it may be of little consequence in reality. They'll gain access to the same string, and it will have 'about' the same time in it when they're done. The overwrite isn't dangerous - bytes in the character array will simply be replaced with the same bytes.

    The problem comes in when the application code depends upon the char * it receives as the return. If it doesn't copy the data immediately, it could be that ANOTHER call is made which overwrites the time with a newer one, and that is a bug, though again, only as serious as the consequence to your application's functioning. It won't be a crash, I'm fairly certain.

    The same thing can happen in standard C use in non-threaded work.

    A better example of the problems related to the subject of thread safety deal with the issues of resource ownership (like memory allocation), and, obviously, overwriting data (which follows into the notion of incrementing/decrementing/altering flags and such outside the control of a lock).

    Any function that operates entirely on locally scoped data is thread safe. There is no need to control access to the function itself. They are also re-entrant.

    Any member function that acts on member data (it probably shouldn't be a non-static member function if it doesn't - read Stroustrup on this point) will have to be considerate of thread safety in either it's implementation or use context.

    Since there is no concept of threading in the definition of the C++ language itself, all management for threadsafety is a function of the discipline of the developer, combined with the minimal support provided in both the operating system and the underlying libraries. For example, when you select the use the multi-threaded runtime library, that's a non-standard option implemented by whatever vendor provided the library, and dependent upon the operating system and CPU services that support it. As such, you can expect that all efforts to enforce thread safety have limited leverage provisions from C++, though important features like private data and private functions do help considerably.

    Many take the view, for example, that one of the reasons we've been hounded upon to set all data private, and then use get/set functions for access, is so we can protect them with a lock within the get/set function. This is a naive but effective approach, if performance isn't paramount and the complexity of the object or its use is limited.

    Sometimes the concept of the boundary of thread safety is broader.

    There may be times when you need to perform a series of operations and call other related functions within a logical operation on a boundary of threadsafety on an entire object. This simply means the object should be locked before the 'outer' function is called (which is a public interface to the world), then all of the functions IT calls should be "interior" to the implementation (private). As long as the subject of the "lock" is one object, you only need to consider the lock on the API exposure - all of the "internal" functions, which are not available to API level code using the object - can be used under the assumption that the object is secured under lock.

    If, on the other hand, you need to operate on a container of objects that is shared among threads, it is not the objects IN the container that must be locked, but the container itself. This can be complicated by the fact that, depending on design, the objects in the container may themselves also be shared OUTSIDE of the container. This is not problematic if those objects are themselves contained by smart pointers (reference counted, like shared_ptr ), and the relationship between the container and the objects it contains is one way - the objects don't 'talk' to their container.

    Otherwise, the "standard" use of STL containers (that is, not smart pointers to objects, but just objects in the container) can be made safe simply by protecting the container. This can be one of the uses of an iterator, perhaps a locking iterator, wrapped around the standard STL iterator you're accustomed to.

    Care should also be taken with regard to naive methods of "overprotection" of data. In the "private data/get-set" approach, where get and set lock at each access, there is the potential for deadlock. This can happen if two locks are required, one after the other, in the situation where one function calls another through a get or set.

    The situation comes about like this. There are two resources to be accessed (they could be ints, strings, whatever). Resource A has it's own mutex (or critical section), Resource B has it's own mutex.

    Thread 1 access resource A, and now has a lock on it. It will soon require access to resource B, but will do so while the lock on A is still being held.

    Thread 2 access resource B, and now has a lock on it. It will soon require access to resource A, but will do so while the lock on B is still being held.

    These two threads will deadlock, because thread 1 will wait on B's release, while thread 2 will wait on A's release - forever.

    For this reason, the very concept of locking should have an application level boundary such that resource acquisition can be controlled. If thread 2 were forced to access the resource in the A-B order, instead of the B-A order, the deadlock could be avoided. If that's not practical, then either it must recognize a deadlock by TRYING a lock on A (it's second step) and releasing B, the scheduling a retry - which isn't practical for application level coding.

    This implies that it is best (and I assure you the results are spectacular) to use or develop objects that help in the implementation of threaded development work. Before the STL and boost, smart pointers were not as well known, but I found them absolutely indispensable for threaded development.


    A great amount of literature covers "lock free" approaches to threaded development. In some cases this is a simple as making certain you can't access a common piece of data from multiple threads. If, for example, you threaded a simple convolution on a bitmap to, say, change it's contrast - you could spin off 1/4 of the bitmap to each of 4 threads, knowing that while it IS a single image, no two threads will overlap by even 1 pixel. This only needs a simple synchronization at the end - so that whatever is to happen AFTER the contrast adjustment is finished doesn't START until all of the threads finish their jobs (sometimes joinable threads are used, sometimes other techniques).

    Another example of a lock free (or, perhaps one might say pseudo lock free) approach is the use of atomic operations. There are, in most modern CPU's, a few assembler instructions that are performed in such a way that the operation is certain to complete without interruption under any circumstance. An example is a decrement and compare against zero. If you're performing reference counting on a smart pointer, you could consider using a 'lock' like a critical section or mutex to protect the integer that counts the references, but it's much slower than an atomic decrement/increment instruction. Instead of a lock, you can use the atomic decrement (when releasing) to guarantee that both the decrement and the comparison of the result, after the decrement, to zero occurs without the possibility of interruption, such that IF the result IS zero, you are known to be safe for deletion of the resource being managed. This is much faster than a lock/decrement/check/release combination. I call foul because there is actually a lock going on here - a refusal to allow the operation to be interrupted - but it's technically about as fast as an assembler decrement and a subsequent compare, and mutexes and locks upon them aren't required. It is a thread safe concept, and is used on the shared_ptr from boost and tr1. Same may not call this a lock free approach, but I've found others that do.

    Obviously the functions new/delete and malloc/free must be from a 'threadsafe' library if threaded development is to be possible at all. This happens to be the case when you select a multi-threaded library, but absolute disaster would result if you were able to launch and use threads without that.

    The reason there are C library calls still available that are NOT carefully thread-safe or thread aware is simply that they are part of the old standards, and it would break convention (and thus old code) if they were altered to be remotely compliant with threaded development (as in asctime and the like).

    One must simply either choose alternatives or encapsulate in such a way that you aren't even tempted to use an interface of such archaic design (violating the notion of threaded access to the function) from your application code. Just because it's there doesn't mean it's appropriate for application to call it. It is part of professional discipline to insist on a coding standard that requires it.
    Last edited by JVene; 05-12-2009 at 03:02 PM.

  2. #17
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Angus, you are paranoid. You're killing yourself with this. Look:

    Quote Originally Posted by Angus View Post
    It does by every definition of thread-safe that's been thrown at me. Foo() cannot be called again until it is finished, and the same criteria applies to goo(). If the definition were extended to imply that nothing else that isn't thread-safe can be trusted to run while foo() and goo() are running, that would be different, but that's not what I've been hearing.
    If programming could be reduced to a set of rules requiring no thinking at all on the part of the programmer (which is what you seem to be asking for), then computers could do it themself. I think the general point here is to bring it to your attention that some "non-safe" functions like asctime() should be understood as such and the reasons for this are blindingly obvious.

    In your example, the problem has to do with the internals of asctime(). By saying, "well, my functions obey the thread safety rules, so how can there be a problem" when you already see a loophole ("what if the two functions are called concurrently"), then "the rules" seem to have served their purpose. It's up to you how to deal with it; eg, there are other ways to get a formatted timestamp.

    It is not that complicated, conceptually (thanks to wyliek for some sanity!); don't drown yourself in vocab.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  3. #18
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by Angus View Post
    That's what I've been saying all along, foo() and goo() are not thread-safe. But because neither of them will experience 2 concurrent invocations they are being handled in a thread-safe manner. However, both might be called concurrently, so there's a problem.
    Correct. For something to be thread-safe, the functions called by that function must also be threadsafe / reentrant. What brewbuck is saying is that foo and goo can only be threadsafe if you can call either of them multiple times simultaneously. Since EITHER of them fail this, there is nothing to do with having OTHER functions involved in the discussion. But it is clearly true that if you have multiple functions SHARING data (e.g. static data in asctime()), they can not be called from different threads.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  4. #19
    Registered User
    Join Date
    May 2007
    Posts
    147
    You can make the use of asctime safe by providing a replacement which locks against a mutex (or similar), copies the result of asctime to a result owned by the caller, then releasing the lock (this is a somewhat standard approach).

    What Angus is talking about recently, though, is that he has a 3rd party library which isn't threadsafe. Using it with threaded programming would require a deep understanding of all the functions of the library, so as to wrap it into something safe - even then, it's only practical if the results are worth it.

    Switching to processes is one solution, but I have no context about what this library does - it could be useful, but IPC could be a mitigating factor.

    It could end up being either necessary to switch to another library, or forgo the benefits of multiprocessing for that case.

  5. #20
    Kung Fu Kitty Angus's Avatar
    Join Date
    Oct 2008
    Location
    Montreal, Canada
    Posts
    115
    Quote Originally Posted by wyliek View Post
    From wikipedia:
    "Thread safety is a computer programming concept applicable in the context of multi-threaded programs. A piece of code is thread-safe if it functions correctly during simultaneous execution by multiple threads. In particular, it must satisfy the need for multiple threads to access the same shared data, and the need for a shared piece of data to be accessed by only one thread at any given time."

    The last sentence is key to your question.
    Thank you for sifting out that useful bit of information. I'd already been through the Wiki page, and was quickly frustrated by the popular and specious definition of "thread-safe". (A problem I frequently have with WP's computing material) That last sentence is, in fact, key, and puts the finer point that I'm looking for.

  6. #21
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by Angus View Post
    I'd already been through the Wiki page, and was quickly frustrated by the popular and specious definition of "thread-safe". (A problem I frequently have with WP's computing material)
    Are you sure you don't just prejudge because it's wikipedia? As you seem to have realized, it's not "specious", it's succinct -- unless you are looking for some kind of legal contract about threading which goes on for pages and appears to make all kinds of guarantees and promises but in the end probably left you even more confused.

    A little while ago I finally pulled down the copy of "Beginning Computer Programming for Dummies" which sits on the shelf at my local library and realized it is actually full of very great, very condensed, "de-mystifiying" informative stuff. However, it is totally lacking in the long winded reverence I see in some CS material. And if you are a sucker for long winded reverence it will be easy to miss the forest for the trees.

    Quote Originally Posted by Angus View Post
    That last sentence is, in fact, key, and puts the finer point that I'm looking for.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  7. #22
    Kung Fu Kitty Angus's Avatar
    Join Date
    Oct 2008
    Location
    Montreal, Canada
    Posts
    115
    "If you use code that isn't thread-safe, and you use threads anywhere else in your program, you are asking for trouble" is pretty succinct to me, and correct. On the other hand "if code isn't thread-safe it just means make sure you don't call it twice at the same time" is no more succinct, and far less correct.

  8. #23
    Registered User Maz's Avatar
    Join Date
    Nov 2005
    Location
    Finland
    Posts
    194
    jv. I loved your explanation, except what comes to atomic operations. I was when you mentioned atomic decrement, i expected seeing a note about 32 bit wide buses almost every machine nowadays have. Since mmm.. I lost the english word.. The = operation for 32 bit volatile variables usually is atomic. Also mentioning compare and swap, which is perhaps the most common building block for more sophisticated atomic ops would have been nice. (and cas is available as an atomic asm instruction for nost processors). And lockless... You could have said that on multi core architectures, atomic instructions often lock the bus for the duration of operation, to prevent other processors from interfering. And finally, those atomic ops built on cas, usually try doing something, and finally check that nothing compromised atomicity. If the doing something was not actually atomic, whole thing is retried. If the operation is long, and constantly fails, the performance may drop dramatically. After longish road trawelled in a jungle of MULTIthreaded c based system, i am starting to believe the threads are like regular expressions. One notices he har a problem. He thinks: i know, ill use regular expressions - now he has two problems...

  9. #24
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Angus View Post
    "If you use code that isn't thread-safe, and you use threads anywhere else in your program, you are asking for trouble" is pretty succinct to me, and correct. On the other hand "if code isn't thread-safe it just means make sure you don't call it twice at the same time" is no more succinct, and far less correct.
    So what are we arguing here? Yes, there's a lot of unclear language on the big wide Internet.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #25
    Registered User Maz's Avatar
    Join Date
    Nov 2005
    Location
    Finland
    Posts
    194
    c'mon Brewwie. This thread has contained some of the best posts I've seen in ages on this board Whether those really were 'answers' to original 'question?' can be argued. But we cant deny the original post drowe discussion into that point ;p
    Last edited by Maz; 05-13-2009 at 11:56 AM.

  11. #26
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by Maz View Post
    c'mon Brewwie. This thread has contained some of the best posts I've seen in ages on this board Whether those really were 'answers' to original 'question?' can be argued. But we cant deny the original post drowe discussion into that point ;p
    Criticism of a single post or even a single sentence therein is not to blackball the entire thread or the poster* -- unless the OP is really God, in which case, besmirching the word of God is to question his Perfection. But I think tabstop has been gone for a while now anyway...

    * and brewbuck is not the only one to notice that Angus really is whiny and paranoid
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  12. #27
    Kung Fu Kitty Angus's Avatar
    Join Date
    Oct 2008
    Location
    Montreal, Canada
    Posts
    115
    Quote Originally Posted by brewbuck View Post
    So what are we arguing here? Yes, there's a lot of unclear language on the big wide Internet.
    Exactly what I said in my last post, that
    If you use code that isn't thread-safe, and you use threads anywhere else in your program, you are asking for trouble
    is right and
    If code isn't thread-safe it just means make sure you don't call it twice at the same time
    is wrong.

    However, the reason I started this thread is that I was wondering what other people mean when they say "thread-safe". I suspect that when someone says their library isn't thread-safe, it is because they want you to follow the latter guidelines. They aren't saying that threads are out of the question anywhere, because they don't use common POSIX globals and things that return pointers to static data, like asctime(). It probably means they use their own globals and statics.

    But, upon further reflection, I realize that I'm using thread-unfriendly data like POSIX globals and such all the time. Errno is one that comes to mind. I use that all the time, and while it is possible (although very difficult) to use it thread-safely in my own code, there's no way to make sure that another library isn't using it while I'm trying to.

    So it seems that perfect thread-safety is impossible, which rubs me the wrong way. I guess the only thing to do is cross your fingers and hope that these race conditions won't cause problems, or if they do, that the problems are minor.
    Last edited by Angus; 05-13-2009 at 12:44 PM.

  13. #28
    .
    Join Date
    Nov 2003
    Posts
    307
    See the table 'MT Interface Safety Levels' mid-way down the page --- discusses what thread-safe and POSIX async-safe mean, and provides a spectrum of 'safe' definitions.

    5 Safe and Unsafe Interfaces (Multithreaded Programming Guide) - Sun Microsystems

    This is from a UNIX point of view, but I do not think that detracts from it's usefulness.

  14. #29
    Registered User
    Join Date
    Dec 2008
    Location
    Black River
    Posts
    128
    Quote Originally Posted by Angus View Post
    Errno is one that comes to mind. I use that all the time, and while it is possible (although very difficult) to use it thread-safely in my own code, there's no way to make sure that another library isn't using it while I'm trying to.
    Which platform are you on? Although the standard generally refrains from speaking about multithreading, most modern implementations will have a per-thread errno variable, which will make it safe to use it across multiple threads.

  15. #30
    Kung Fu Kitty Angus's Avatar
    Join Date
    Oct 2008
    Location
    Montreal, Canada
    Posts
    115
    Quote Originally Posted by Ronix View Post
    Which platform are you on? Although the standard generally refrains from speaking about multithreading, most modern implementations will have a per-thread errno variable, which will make it safe to use it across multiple threads.
    No kidding? Linux is the one I have in mind. How does a global like errno duplicate itself and make itself unique to each running thread? Globals are part of the data segment, right? And each thread uses the same data segment as every other thread in that process, right? So what am I missing?

Popular pages Recent additions subscribe to a feed

Tags for this Thread