Take this example of an object in C:
/* object.h */
typedef struct Object Object;
Object* createObject();
void freeObject(Object* object);
int getObjectNumber(Object* object);
void incrementObjectNumber(Object* object);
It is a very simple opaque type that stores a number and can increment it.
In order to make my code thread safe, I have two options. The first is to store a mutex within Object:
void
func(Object* object)
{
incrementObject(object);
}
int
main()
{
Object* object = createObject();
Thread thread1 = startThread(func, object);
Thread thread2 = startThread(func, object);
waitThread(thread1);
waitThread(thread2);
freeObject(object);
}
The second is to store a mutex in main:
void
func(Object* object, Mutex mutex)
{
lockMutex(mutex);
incrementObject(object);
unlockMutex(mutex);
}
int
main()
{
Object* object = createObject();
Mutex mutex;
Thread thread1 = startThread(func, object, mutex);
Thread thread2 = startThread(func, object, mutex);
waitThread(thread1);
waitThread(thread2);
freeObject(object);
}
Which one is better practice, if there will only ever be one Object?
No matter if there is one object or several, it is a good idea to store the mutex together with the opaque object. First of all, the mutex and the data it protects belong together. But also, thread-safety needs to be dealt with by your "ADT" and not by the caller. Access to objects should only occur through setters/getters, which will handle thread-safety internally. In this case incrementObject.
That way you may declare that your whole library is thread-safe, it takes care of itself rather than dumping the responsibility on someone else. Your 1st example has a much cleaner API, while the 2nd one requires the caller to drag around a mutex into their user-defined callback, which isn't a clean solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With