15

imagine I write a library in C. Further, imagine this library to be used from a multi-threaded environment. How do I make it thread-safe? More specific: How do I assure, that certain functions are executed only by one thread at a time?

In opposite to Java or C# for example, C has no means to deal with threads/locks/etc., nor does the C standard library. I know, that operating systems support threads, but using their api would restrict the compatibility of my library very much. Which possibilities do I have, to keep my library as compatible/portable as possible? (for example relying on OpenMP, or on Posix threads to keep it compatible with at least all unix-like operating systems?)

Matthew Murdoch
  • 30,874
  • 30
  • 96
  • 127
Dave O.
  • 2,231
  • 3
  • 21
  • 25
  • 2
    You'll need a library. pthreads is the usual *nix choice. – Hans Passant Jan 08 '10 at 18:05
  • 1
    There are a lot of ways to ensure thread safety without using a threading API. What operating systems and cpu architectures are you planning on supporting? – Beanz Jan 08 '10 at 19:25
  • I would like to support unix like systems (especially linux and mac os x) and windows. I don't need sophisticated means to deal with threads. Simple locks and the possibility for threads to queue for locks are sufficient – Dave O. Jan 08 '10 at 20:12
  • I have forgotten to mention the cpus: intel and amd mainstream cpus. Though, I wonder why Your answer depends on this information.... (I'm not capable of writing assembler.) – Dave O. Jan 08 '10 at 20:21
  • There are solutions to make a library thread safe, not using any of the locking mechanism. One of the way is to make sure there are no statisized variables in the function call, and no global or static global variables. – John Nov 07 '13 at 12:26

10 Answers10

23

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
  • 44,018
  • 30
  • 122
  • 156
  • What's up today with everyone downvoting me for no apparent reason? :/ – Andreas Bonini Jan 08 '10 at 18:17
  • 7
    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 Jan 08 '10 at 18:30
  • thx for Your answer. From Your knowledge, does one have to write much thread related code to support both windows and posix? – Dave O. Jan 08 '10 at 20:09
  • @Dave: no, not really. That + ~200 lines to handle thread creation and destruction is all I have. – Andreas Bonini Jan 08 '10 at 20:12
  • +1 from my side as well... A bit simplistic but it is what the OP needs. – Fredrik Jan 09 '10 at 10:29
5

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
  • 74,869
  • 16
  • 134
  • 187
4

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
  • 273,490
  • 39
  • 460
  • 699
  • +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 Jan 11 '10 at 02:07
3

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

Andres
  • 3,324
  • 6
  • 27
  • 32
3

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
  • 68,631
  • 21
  • 159
  • 205
3

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).

Beanz
  • 1,957
  • 1
  • 13
  • 14
  • 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 O. Jan 08 '10 at 22:23
  • 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. – Beanz Jan 08 '10 at 23:52
  • Thx, I didn't know that there is a common instructions set among cpus – Dave O. Jan 09 '10 at 11:12
  • 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 Jan 11 '10 at 02:03
  • 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. – Beanz Jan 11 '10 at 02:23
  • 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 Jan 11 '10 at 04:45
1

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
  • 554,122
  • 78
  • 1,158
  • 1,373
  • thx for providing the link! Do you have more documents, that teach, how to write minimal wrappers around existing threading apis? – Dave O. Jan 08 '10 at 20:08
  • 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 Jan 08 '10 at 20:28
0

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.

calvinlough
  • 362
  • 3
  • 13
  • Can't I implement very simple locking mechanisms by myself, that I'm able to map 1:1 on Posix/WinApi using #define? – Dave O. Jan 08 '10 at 20:06
  • Yeah, I suppose that would work. There might be slight differences in functionality but that probably wouldn't be that difficult to sort out. – calvinlough Jan 08 '10 at 21:44
0

"imagine I write a library in C. Further, imagine this library to be used from a multi-threaded environment. How do I make it thread-safe? More specific: How do I assure, that certain functions are executed only by one thread at a time?"

You can't -> write a thread-safe or better re-entrant functions. Unless, You would like to write system-wide locks - a very bad idea.

"In opposite to Java or C# for example, C has no means to deal with threads/locks/etc."

This is a joke - right? Long before the Java and C# was developed, the locks were invented and widely used as an synchronization objects...

"I know, that operating systems support threads, but using their api would restrict the compatibility of my library very much."

The thing is, that such libraries already exists - f.e. wxWidgets, which are offering the portable wxThread... (but this is C++)

Anyway, there are 2 main "flavours" of C: the ANSI C and the GNU C -> two different worlds... pick one or the other.

vtomazzi
  • 9
  • 1
  • 4
    This is quite a poor answer. You didn't answer the question but rather blurt out meaningless defenses of C. "Just write better code", "System-wide locks are bad anyways", etc. – RecursiveExceptionException Jan 23 '19 at 18:24
0

A different approach from blocking:

If you are designing the library from scratch just implement all functions reentrant (at least implicitly). This way not only your library can be used in a multi-threaded environment (with some care) but also it would be very efficient for multithreading (no need to block code to make functions thread-safe).

Notice that the interface would be a bit more complex since all internal and external context needed between successive function calls must be kept by the caller (and passed to the functions as value-return arguments).

MrIo
  • 108
  • 1
  • 10