问题
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?)
回答1:
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;
};
回答2:
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.
回答3:
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.
回答4:
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.
回答5:
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).
回答6:
You also should avoid static and global variables that can be modified avoiding synchronization code all over your module
回答7:
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.
回答8:
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.
回答9:
"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.
来源:https://stackoverflow.com/questions/2029605/thread-safety-in-c