1
\$\begingroup\$

I wanted to get some thread support on windows with very simple headers for a library that I'm working on but it had to work on both Windows and Linux. Since C11 threads are not widely supported, this was a great opportunity to learn more about the Windows API and POSIX threads. I decided to post this here because I'm not very experienced with threads and mutexes from both Windows and POSIX and I'd like it to be reviewed. Some things are omitted because they are part of the aforementioned library.

flags.h

/**
 * cmc_flags
 *
 * Defines common error codes used by the entire library.
 */
static struct
{
    int OK;        // No errors
    int THREAD;    // Generic error regarding threads
    int MUTEX;     // Generic error regarding mutexes
} cmc_flags = { 0, /* Omitted */ 8, 9 };

mutex.h

/**
 * A very simple, header-only and minimalistic cross-platform mutex
 *
 * Types
 *  - cmc_mutex
 *
 * Functions
 *  - cmc_mtx_init
 *  - cmc_mtx_destroy
 *  - cmc_mtx_lock
 *  - cmc_mtx_unlock
 *  - cmc_mtx_trylock
 */

#include <stdbool.h>

#include "flags.h"

#if defined(_WIN32)
#define CMC_MUTEX_WINDOWS
#elif defined(__unix__)
#define CMC_MUTEX_UNIX
#else
#error "Unknown platform for CMC Mutex"
#endif

/* Platform specific includes */
#if defined(CMC_MUTEX_WINDOWS)
#include <windows.h>
#elif defined(CMC_MUTEX_UNIX)
#include <errno.h>
#include <pthread.h>
#endif

/**
 * struct cmc_mutex
 *
 * A mutex wrapper.
 */
struct cmc_mutex
{
#if defined(CMC_MUTEX_WINDOWS)
    HANDLE mutex;
#elif defined(CMC_MUTEX_UNIX)
    pthread_mutex_t mutex;
#endif
    int flag;
};

/**
 * Acquire resources for a mutex.
 *
 * \param mtx An uninitialized mutex wrapper.
 * \return True or false if the mutex was successfully initialized.
 */
static inline bool cmc_mtx_init(struct cmc_mutex *mtx)
{
#if defined(CMC_MUTEX_WINDOWS)
    mtx->mutex = CreateMutex(NULL, FALSE, NULL);

    if (mtx->mutex == NULL)
        mtx->flag = cmc_flags.MUTEX;
    else
        mtx->flag = cmc_flags.OK;

    return mtx->mutex != NULL;

#elif defined(CMC_MUTEX_UNIX)
    int err = pthread_mutex_init(&(mtx->mutex), NULL);

    if (err != 0)
        mtx->flag = cmc_flags.MUTEX;
    else
        mtx->flag = cmc_flags.OK;

    return err == 0;
#endif
}

/**
 * Release all resources from a mutex. Calling this function on a locked mutex
 * causes undefined behavior.
 *
 * \param mtx A mutex to be destroyed.
 * \return True or false if the mutex was successfully destroyed.
 */
static inline bool cmc_mtx_destroy(struct cmc_mutex *mtx)
{
#if defined(CMC_MUTEX_WINDOWS)
    if (!CloseHandle(mtx->mutex))
    {
        mtx->flag = cmc_flags.MUTEX;
        return false;
    }

    mtx->flag = cmc_flags.OK;
    return true;

#elif defined(CMC_MUTEX_UNIX)
    if (pthread_mutex_destroy(&(mtx->mutex)) != 0)
    {
        mtx->flag = cmc_flags.MUTEX;
        return false;
    }

    mtx->flag = cmc_flags.OK;
    return true;
#endif
}

/**
 * Locks a mutex. If the mutex is already locked, the thread is blocked until
 * the mutex can be acquired.
 *
 * \param mtx A mutex to be locked.
 * \return True or false if the lock on the mutex was successfully acquired.
 */
static inline bool cmc_mtx_lock(struct cmc_mutex *mtx)
{
#if defined(CMC_MUTEX_WINDOWS)
    DWORD result = WaitForSingleObject(mtx->mutex, INFINITE);

    if (result == WAIT_FAILED || result == WAIT_ABANDONED)
    {
        mtx->flag = cmc_flags.MUTEX;
        return false;
    }

    mtx->flag = cmc_flags.OK;
    return true;

#elif defined(CMC_MUTEX_UNIX)
    if (pthread_mutex_lock(&mtx->mutex) != 0)
    {
        mtx->flag = cmc_flags.MUTEX;
        return false;
    }

    mtx->flag = cmc_flags.OK;
    return true;
#endif
}

/**
 * Unlocks a mutex. If the mutex is already locked, the thread is blocked until
 * the mutex can be acquired.
 *
 * \param mtx A mutex to be unlocked.
 * \return True or false if the mutex was successfully unlocked.
 */
static inline bool cmc_mtx_unlock(struct cmc_mutex *mtx)
{
#if defined(CMC_MUTEX_WINDOWS)
    if (!ReleaseMutex(mtx->mutex))
        mtx->flag = cmc_flags.MUTEX;
    else
        mtx->flag = cmc_flags.OK;

    return mtx->flag == cmc_flags.OK;

#elif defined(CMC_MUTEX_UNIX)
    if (pthread_mutex_unlock(&mtx->mutex) != 0)
        mtx->flag = cmc_flags.MUTEX;
    else
        mtx->flag = cmc_flags.OK;

    return mtx->flag == cmc_flags.OK;
#endif
}

/**
 * Tries to lock a mutex. If the mutex is already locked, the thread is not
 * blocked and the function returns false.
 *
 * \param mtx A mutex to try to lock.
 * \return True or false if the lock on the mutex was successfully acquired.
 */
static inline bool cmc_mtx_trylock(struct cmc_mutex *mtx)
{
#if defined(CMC_MUTEX_WINDOWS)
    DWORD result = WaitForSingleObject(mtx->mutex, 0);

    if (result == WAIT_FAILED || result == WAIT_ABANDONED)
    {
        mtx->flag = cmc_flags.MUTEX;
        return false;
    }

    mtx->flag = cmc_flags.OK;
    return result != WAIT_TIMEOUT;

#elif defined(CMC_MUTEX_UNIX)
    int err = pthread_mutex_trylock(&mtx->mutex);

    if (err == EINVAL || err == EFAULT)
        mtx->flag = cmc_flags.MUTEX;
    else
        mtx->flag = cmc_flags.OK;

    return err == 0;
#endif
}

thread.h

/**
 * A very simple, header-only utility for spawning and joining threads
 *
 * Types
 *  - cmc_thread
 *  - cmc_thread_proc
 *
 * Functions
 *  - cmc_thrd_create
 *  - cmc_thrd_join
 */

#include <inttypes.h>
#include <stdbool.h>

#include "flags.h"

#if defined(_WIN32)
#define CMC_THREAD_WINDOWS
#elif defined(__unix__)
#define CMC_THREAD_UNIX
#else
#error "Unknown platform for CMC Threads"
#endif

#if defined(CMC_THREAD_WINDOWS)
#include <windows.h>
#elif defined(CMC_THREAD_UNIX)
#include <pthread.h>
#endif

/* The type of a process run by a thread */
typedef int (*cmc_thread_proc)(void *);

/**
 * struct cmc_thread
 *
 * A thread wrapper.
 */
struct cmc_thread
{
#if defined(CMC_THREAD_WINDOWS)
    HANDLE thread;
#elif defined(CMC_THREAD_UNIX)
    pthread_t thread;
#endif
    int flag;
    void *args;
    cmc_thread_proc process;
};

/* A thread wrapper */
#if defined(CMC_THREAD_WINDOWS)
static DWORD cmc_thread_wrapper(void *arg)
#elif defined(CMC_THREAD_UNIX)
static void *cmc_thread_wrapper(void *arg)
#endif
{
    struct cmc_thread *thr = (struct cmc_thread *)arg;

    int result = thr->process(thr->args);

#if defined(CMC_THREAD_WINDOWS)
    return result;
#elif defined(CMC_THREAD_UNIX)
    return (void *)(intptr_t)result;
#endif
}

/**
 * Creates and spawns a new thread.
 *
 * \param thr  An uninitialized thread wrapper.
 * \param proc Pointer to a function that will run in a new thread.
 * \param args The arguments passed to the proc function.
 * \return True or false if the thread creation was successfull.
 */
static inline bool cmc_thrd_create(struct cmc_thread *thr, cmc_thread_proc proc,
                                   void *args)
{
#if defined(CMC_THREAD_WINDOWS)
    thr->args = args;
    thr->process = proc;
    thr->thread = CreateThread(NULL, 0, cmc_thread_wrapper, thr, 0, NULL);

    if (!thr->thread)
        thr->flag = cmc_flags.THREAD;
    else
        thr->flag = cmc_flags.OK;

    return thr->flag == cmc_flags.OK;

#elif defined(CMC_THREAD_UNIX)
    thr->args = args;
    thr->process = proc;
    if (pthread_create(&(thr->thread), NULL, cmc_thread_wrapper, thr) != 0)
        thr->flag = cmc_flags.THREAD;
    else
        thr->flag = cmc_flags.OK;

    return thr->flag == cmc_flags.OK;
#endif
}

/**
 * Blocks the current thread until the target thread terminates.
 *
 * \param thr    The target thread to wait on.
 * \param result The value returned by the target thread. Optional.
 * \return True or false if the join was successfull.
 */
static inline bool cmc_thrd_join(struct cmc_thread *thr, int *result)
{
#if defined(CMC_THREAD_WINDOWS)
    if (WaitForSingleObject(thr->thread, INFINITE) == WAIT_FAILED)
    {
        thr->flag = cmc_flags.THREAD;
        return false;
    }

    DWORD exit_code;

    if (result)
    {
        if (GetExitCodeThread(thr->thread, &exit_code) == 0)
        {
            thr->flag = cmc_flags.THREAD;
            return false;
        }
        else
            *result = (int)exit_code;
    }

    thr->flag = cmc_flags.OK;

    if (CloseHandle(thr->thread) == 0)
    {
        thr->flag = cmc_flags.THREAD;
        return false;
    }

    thr->flag = cmc_flags.OK;
    return true;

#elif defined(CMC_THREAD_UNIX)
    void *exit_value;

    if (pthread_join(thr->thread, &exit_value) != 0)
    {
        thr->flag = cmc_flags.THREAD;
        return false;
    }

    if (result)
    {
        *result = (int)(intptr_t)exit_value;
    }

    thr->flag = cmc_flags.OK;
    return true;

#endif
}

example.c

#include <stdio.h>
#include <stdlib.h>
#include "mutex.h"
#include "thread.h"

#define NTHREADS 1000

struct cmc_mutex lck;

/* How many trylock failed */
struct cmc_mutex flck;
int trylock_fails = 0;

int thread_process(void *arg)
{
    int *sum = (int *)arg;

    if (rand() % 2 == 0)
    {
        cmc_mtx_lock(&lck);
        for (int i = 0; i < 1000; i++)
            *sum += 1;
        cmc_mtx_unlock(&lck);
    }
    else
    {
        if (!cmc_mtx_trylock(&lck))
        {
            cmc_mtx_lock(&flck);
            trylock_fails += 1;
            cmc_mtx_unlock(&flck);

            return 0;
        }

        for (int i = 0; i < 1000; i++)
            *sum += 1;

        cmc_mtx_unlock(&lck);
    }

    return 0;
}

int main(void)
{
    cmc_mtx_init(&lck);
    cmc_mtx_init(&flck);

    struct cmc_thread threads[NTHREADS];

    int sum = 0;

    for (int i = 0; i < NTHREADS; i++)
        cmc_thrd_create(&threads[i], thread_process, &sum);

    for (int i = 0; i < NTHREADS; i++)
        cmc_thrd_join(&threads[i], NULL);

    printf("Total sum       : %d\n", sum);
    printf("Failed trylocks : %d\n", trylock_fails);
}
```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.