From 9bd10281119a28323ddd05bd13827bceb553cc56 Mon Sep 17 00:00:00 2001 From: "@syxhe" Date: Thu, 17 Apr 2025 21:32:35 -0500 Subject: Start work on implementing a threadpool --- src/threadpool.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/threadpool.c (limited to 'src/threadpool.c') diff --git a/src/threadpool.c b/src/threadpool.c new file mode 100644 index 0000000..8f1cf0b --- /dev/null +++ b/src/threadpool.c @@ -0,0 +1,109 @@ +#include "threadpool.h" +#include "shared.h" + +#include +#include +#include + +/* Mutex: Lock a shared resource. Used to prevent race conditions when accessing / modifying some shared resource. A lock must +// always be followed by an unlock + +// Semaphore: Send / wait on a signal; solves the consumer/producer problem. A function that sends should never wait, and a +// function that waits should never send */ + +// Pair some data with a mutex. Specifically a way to deal with mutices easier, not for data storage (mtxpair_free does not free the `(void*)data` member) +typedef struct mtxp { + void *data; + mtx_t *mtx; +} mtxpair; + +mtxpair * mtxpair_init(void * const data, int type) { + mtxpair *mtxp = malloc(1 * sizeof(*mtxp)); + if(!mtxp) + return NULL; + + // Make room for the mutex + mtxp->mtx = malloc(1 * sizeof(*mtxp->mtx)); + if(!mtxp->mtx) { + free(mtxp); + return NULL; + } + + // Init the mutex + if(mtx_init(mtxp->mtx, type) == thrd_error) { + free(mtxp); free(mtxp->mtx); + RETURNWERR(errno, NULL); + } + + mtxp->data = data; + return mtxp; +} + +void mtxpair_free(mtxpair *mp) { + if(!mp) + return; + + mtx_destroy(mp->mtx); + free(mp->mtx); + free(mp); + + return; +} + +int mtxpair_setdata(mtxpair * const mp, void * const data) { + if(!mp) + RETURNWERR(EINVAL, -1); + + mp->data = data; + return 0; +} + + +// thrd_create which calls mtx_lock/unlock on `arg` automatically +int thrd_createwmx(thrd_t * const thr, thrd_start_t func, mtxpair * const mtxd) { + if(!thr) + RETURNWERR(EINVAL, thrd_error); + if(!func) + RETURNWERR(EINVAL, thrd_error); + if(!mtxd) + RETURNWERR(EINVAL, thrd_error); + + if(mtx_lock(mtxd->mtx) == thrd_error) {RETURNWERR(errno, thrd_error);} + int retval = thrd_create(thr, func, mtxd->data); + if(mtx_unlock(mtxd->mtx) == thrd_error) {RETURNWERR(errno, thrd_error);} + + return retval; +} + + +/* Ok, after doing a little more research, the best way to do this is probaby via a producer/consumer architecture. Spawn a bunch of +// threads waiting on a queue (via semaphore) and when one is notified pop a task of the queue and execute it. In this case, the +// producer would be the filesystem scanner funciton providing new files to encrypt, and the consumers would be threads waiting +// to encrypt them */ + +// Threadpool: + // Array of threads + // Task Queue + // Readiness semaphore + // Linked List of Tasks + // Task: + // int (*callback)(void*) + // void *arg + +// Here's a good reference of this implemented in C++ using Boost: https://gist.github.com/mikeando/482342 + +typedef struct cq { + +} cqueue; + +typedef struct task { + int (*callback)(void*); + void *arg; +} task; +typedef struct tp { + thrd_t **threads; + int nthreads; + + cqueue *taskqueue; +} threadpool; + -- cgit v1.2.3