views:

260

answers:

8
+2  Q: 

Thread Safety in C

+4  A: 

You will need to use your OS's threading library. On Posix, that will usually be pthreads and you'll want pthread_mutex_lock.

Windows has it's own threading library and you'll want to look at either critical sections or CreateMutex. Critical sections are more optimized but are limited to a single process and you can't use them in WaitForMultipleObjects.

R Samuel Klatchko
thx for the clarification and the pointers to initial material on the topic
Dave
+4  A: 

You can create wrappers with #ifdef. It's really the best you can do. (Or you can use a third party library to do this).

I'll show how I did it as an example for windows and linux. It's in C++ and not C but again it's just an example:

#ifdef WIN32
typedef HANDLE thread_t;
typedef unsigned ThreadEntryFunction;
#define thread __declspec(thread)

class Mutex : NoCopyAssign
{
public:
    Mutex() { InitializeCriticalSection(&mActual); }
    ~Mutex() { DeleteCriticalSection(&mActual); }
    void Lock() { EnterCriticalSection(&mActual); }
    void Unlock() { LeaveCriticalSection(&mActual); }
private:
    CRITICAL_SECTION mActual;
};

class ThreadEvent : NoCopyAssign
{
public:
    ThreadEvent() { Actual = CreateEvent(NULL, false, false, NULL); }
    ~ThreadEvent() { CloseHandle(Actual); }
    void Send() { SetEvent(Actual); }

    HANDLE Actual;
};
#else
typedef pthread_t thread_t;
typedef void *ThreadEntryFunction;
#define thread __thread
extern pthread_mutexattr_t MutexAttributeRecursive;

class Mutex : NoCopyAssign
{
public:
    Mutex() { pthread_mutex_init(&mActual, &MutexAttributeRecursive); }
    ~Mutex() { pthread_mutex_destroy(&mActual); }
    void Lock() { pthread_mutex_lock(&mActual); }
    void Unlock() { pthread_mutex_unlock(&mActual); }
private:
    pthread_mutex_t mActual;
};

class ThreadEvent : NoCopyAssign
{
public:
    ThreadEvent() { pthread_cond_init(&mActual, NULL); }
    ~ThreadEvent() { pthread_cond_destroy(&mActual); }

    void Send() { pthread_cond_signal(&mActual); }
private:
    pthread_cond_t mActual;
};

inline thread_t GetCurrentThread() { return pthread_self(); }
#endif

/* Allows for easy mutex locking */
class MutexLock : NoAssign
{
public:
    MutexLock(Mutex &m) : mMutex(m) { mMutex.Lock(); }
    ~MutexLock() { mMutex.Unlock(); }
private:
    Mutex &mMutex;
};
Andreas Bonini
What's up today with everyone downvoting me for no apparent reason? :/
Andreas Bonini
they do it for any number of reasons, likely they aren't getting laid or are angry at their boss -- not to worry I'll "fix" that damage as best I can since they gave you no critical justification for it
Hardryv
thx for Your answer. From Your knowledge, does one have to write much thread related code to support both windows and posix?
Dave
@Dave: no, not really. That + ~200 lines to handle thread creation and destruction is all I have.
Andreas Bonini
+1 from my side as well... A bit simplistic but it is what the OP needs.
Fredrik
+1  A: 

If your goal is to be compatible on unix-like operating systems, I would use POSIX threading.

That being said, if you want to support windows as well, you'll need to have two code paths for this - pthreads on unix and Windows threads on Windows. It's fairly easy to just make your own "thread library" to wrap these.

There are quite a few that do this (like OpenThreads), but most of them I've used are C++, not C.

Reed Copsey
thx for providing the link! Do you have more documents, that teach, how to write minimal wrappers around existing threading apis?
Dave
I'd grab OpenThreads - it's C++, but it handles the calls well. It'd be easy to look at that, and make a C version (since it's quite small, but handles things very well, and is very robust.)
Reed Copsey
A: 

Using Posix threads sounds like a good idea to me (but I'm no expert). In particular, Posix has good primitives for ensuring mutual exclusion.

If you had to create a library without any dependencies, you would have to implement the mutual exclusion algorithms yourself, which is a bad idea.

stealthdragon
Can't I implement very simple locking mechanisms by myself, that I'm able to map 1:1 on Posix/WinApi using #define?
Dave
Yeah, I suppose that would work. There might be slight differences in functionality but that probably wouldn't be that difficult to sort out.
stealthdragon
+2  A: 

You also should avoid static and global variables that can be modified avoiding synchronization code all over your module

Andres
+1  A: 

It is a misconception that the pthreads library doesn't work on Windows. Check out sourceforge.net. I would recommend pthreads because it is cross-platform and its mutexes are way faster than e.g. the Windows builtin mutexes.

AndiDog
+2  A: 

You have two main options:

1) You specify which multi-threaded environment your library is thread-safe in, and use the synchronisation functions of that environment.

2) You specify that your library is not thread-safe. If your caller wants to use it in a multi-threaded environment, then it's their responsibility to make it thread-safe, by using external synchronisation if necessary to serialise all calls to your library. If your library uses handles and doesn't need any global state, this might for instance mean that if they have a handle they only use in a single thread, then they don't need any synchronisation on that handle, because it's automatically serialised.

Obviously you can take a multi-pack approach to (1), and use compile-time constants to support all the environments you know about.

You could also use a callback architecture, link-time dependency, or macros, to let your caller tell you how to synchronise. This is kind of a mixture of (1) and (2).

But there's no such thing as a standard multi-threaded environment, so it's pretty much impossible to write self-contained code that is thread-safe everywhere unless it's completely stateless (that is, the functions are all side-effect free). Even then you have to interpret "side-effect" liberally, since of course the C standard does not define which library functions are thread-safe. It's a bit like asking how to write C code which can execute in a hardware interrupt handler. "What's an interrupt?", you might very well ask, "and what things that I might do in C aren't valid in one?". The only answers are OS-specific.

Steve Jessop
+1. Number 2) isn't actually as bad as it sounds - if you have functions like `doSomethingToBuffer(char *buffer, int option);`, then you can say "it's thread-safe as long as you don't call it on the same buffer from two different threads at the same time".
caf
+2  A: 

Write your own lock.

Since you're targeting PCs you're dealing with the x86 architecture which natively supplies all the multi-threading support you should need. Go over your code and identify any functions that have shared resources. Give each shared resource a 32-bit counter. Then using the interlocked operations that are implemented by the CPUs keep track of how many threads are using each shared resource and make any thread that wants to use a shared resource wait until the resource is released.

Here's a really good blog post about interlocked operations: Using Interlocked Instructions from C/C++

The author focuses mostly on using the Win32 Interlocked wrappers, but pretty much every operating system has their own wrappers for the interlocked operations, and you can always write the assembly (each of these operations is only one instruction).

Chris
Thx for the interesting link. However, if I understand You correctly, I would have to write assembly to circumvent operating system api dependencies. But wouldn't this introduce cpu model dependencies? From Your experience, what would be less work: Providing support for multiple OS threading apis or maintaining cpu specific asm blocks in my library?
Dave
Since you're only talking about running on PCs, you are pretty much only talking about one CPU architecture x86. Unless you intend to support pre-pentium 4 processors, I'd say it is a lot less work to write the x86 assembly once than to write 3+ c++ implementations.
Chris
Thx, I didn't know that there is a common instructions set among cpus
Dave
The part where this falls down is at *"...and make any thread that wants to use a shared resource wait until the resource is released."* To do that you will either need to invoke the OS / threading-library specific sleep function, or busy-wait - which is awful.
caf
I don't really agree with you here. Depending on the types of operations that the library is going to execute with the shared resources it may make perfect sense to do busy-waits (or as they're more commonly called spinlocks). If the operations are sufficiently small that the resources will only be blocked for a very short period of time spinlocks are preferable due to the performance implications of writing the thread's stack to main memory when the thread enters the wait state, and reloading it to evaluate the lock and potentially begin execution again. It all depends on what you're doing.
Chris
Spinlocks aren't going to be great if it's ever run on a single-core machine, because when you're unlucky enough for a thread to get preempted while its holding a lock (and yes this will be minimised if the critical sections are small enough, but sooner or later it will happen), the "waiting" thread will then spin for the entire remainder of its timeslice before the "locked" thread gets a chance to run again. I agree though that it all depends on what you're doing, I just don't think that userspace spinlocks are a good general purpose solution.
caf