#include <assert.h>
#include "lz4conf.h"
#include "threadpool.h"
#if defined(_MSC_VER)
# pragma warning(disable : 4204)
#endif
#if !LZ4IO_MULTITHREAD
struct TPool_s {
int dummy;
};
static TPool g_poolCtx;
TPool* TPool_create(int numThreads, int queueSize) {
(void)numThreads;
(void)queueSize;
return &g_poolCtx;
}
void TPool_free(TPool* ctx) {
assert(!ctx || ctx == &g_poolCtx);
(void)ctx;
}
void TPool_submitJob(TPool* ctx, void (*job_function)(void*), void* arg) {
(void)ctx;
job_function(arg);
}
void TPool_jobsCompleted(TPool* ctx) {
assert(!ctx || ctx == &g_poolCtx);
(void)ctx;
}
#elif defined(_WIN32)
#include <windows.h>
typedef struct TPool_s {
HANDLE completionPort;
HANDLE* workerThreads;
int nbWorkers;
int queueSize;
LONG nbPendingJobs;
HANDLE jobSlotAvail;
HANDLE allJobsCompleted;
} TPool;
void TPool_free(TPool* pool)
{
if (!pool) return;
{ int i;
for (i = 0; i < pool->nbWorkers; i++) {
PostQueuedCompletionStatus(pool->completionPort, 0, 0, NULL);
}
}
WaitForMultipleObjects(pool->nbWorkers, pool->workerThreads, TRUE, INFINITE);
{ int i;
for (i = 0; i < pool->nbWorkers; i++) {
CloseHandle(pool->workerThreads[i]);
}
}
free(pool->workerThreads);
CloseHandle(pool->completionPort);
CloseHandle(pool->jobSlotAvail);
CloseHandle(pool->allJobsCompleted);
free(pool);
}
static DWORD WINAPI WorkerThread(LPVOID lpParameter)
{
TPool* const pool = (TPool*)lpParameter;
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
while (GetQueuedCompletionStatus(pool->completionPort,
&bytesTransferred, &completionKey,
&overlapped, INFINITE)) {
if (overlapped == NULL) { break; }
((void (*)(void*))completionKey)(overlapped);
if (InterlockedDecrement(&pool->nbPendingJobs) == 0) {
SetEvent(pool->allJobsCompleted);
}
ReleaseSemaphore(pool->jobSlotAvail, 1, NULL);
}
return 0;
}
TPool* TPool_create(int nbWorkers, int queueSize)
{
TPool* pool;
if (nbWorkers <= 0 || queueSize <= 0) return NULL;
if (nbWorkers>LZ4_NBWORKERS_MAX) nbWorkers=LZ4_NBWORKERS_MAX;
pool = calloc(1, sizeof(TPool));
if (!pool) return NULL;
pool->completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, nbWorkers);
if (!pool->completionPort) { goto _cleanup; }
pool->nbWorkers = nbWorkers;
pool->workerThreads = (HANDLE*)malloc(sizeof(HANDLE) * nbWorkers);
if (pool->workerThreads == NULL) { goto _cleanup; }
{ int i;
for (i = 0; i < nbWorkers; i++) {
pool->workerThreads[i] = CreateThread(NULL, 0, WorkerThread, pool, 0, NULL);
if (!pool->workerThreads[i]) { goto _cleanup; }
}
}
pool->queueSize = queueSize;
pool->nbPendingJobs = 0;
pool->jobSlotAvail = CreateSemaphore(NULL, queueSize+nbWorkers, queueSize+nbWorkers, NULL);
if (!pool->jobSlotAvail) { goto _cleanup; }
pool->allJobsCompleted = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!pool->allJobsCompleted) { goto _cleanup; }
return pool;
_cleanup:
TPool_free(pool);
return NULL;
}
void TPool_submitJob(TPool* pool, void (*job_function)(void*), void* arg)
{
assert(pool);
WaitForSingleObject(pool->jobSlotAvail, INFINITE);
ResetEvent(pool->allJobsCompleted);
InterlockedIncrement(&pool->nbPendingJobs);
PostQueuedCompletionStatus(pool->completionPort,
0,
(ULONG_PTR)job_function,
(LPOVERLAPPED)arg);
}
void TPool_jobsCompleted(TPool* pool)
{
assert(pool);
WaitForSingleObject(pool->allJobsCompleted, INFINITE);
}
#else
#include <stdlib.h>
#include <pthread.h>
typedef struct TPool_job_s {
void (*job_function)(void*);
void *arg;
} TPool_job;
struct TPool_s {
pthread_t* threads;
size_t threadCapacity;
size_t threadLimit;
TPool_job* queue;
size_t queueHead;
size_t queueTail;
size_t queueSize;
size_t numThreadsBusy;
int queueEmpty;
pthread_mutex_t queueMutex;
pthread_cond_t queuePushCond;
pthread_cond_t queuePopCond;
int shutdown;
};
static void TPool_shutdown(TPool* ctx);
void TPool_free(TPool* ctx) {
if (!ctx) { return; }
TPool_shutdown(ctx);
pthread_mutex_destroy(&ctx->queueMutex);
pthread_cond_destroy(&ctx->queuePushCond);
pthread_cond_destroy(&ctx->queuePopCond);
free(ctx->queue);
free(ctx->threads);
free(ctx);
}
static void* TPool_thread(void* opaque);
TPool* TPool_create(int nbThreads, int queueSize)
{
TPool* ctx;
if (nbThreads<1 || queueSize<1) { return NULL; }
ctx = (TPool*)calloc(1, sizeof(TPool));
if (!ctx) { return NULL; }
{ int error = 0;
error |= pthread_mutex_init(&ctx->queueMutex, NULL);
error |= pthread_cond_init(&ctx->queuePushCond, NULL);
error |= pthread_cond_init(&ctx->queuePopCond, NULL);
if (error) { TPool_free(ctx); return NULL; }
}
ctx->queueSize = (size_t)queueSize + 1;
ctx->queue = (TPool_job*)calloc(1, ctx->queueSize * sizeof(TPool_job));
if (ctx->queue == NULL) {
TPool_free(ctx);
return NULL;
}
ctx->queueHead = 0;
ctx->queueTail = 0;
ctx->numThreadsBusy = 0;
ctx->queueEmpty = 1;
ctx->shutdown = 0;
ctx->threads = (pthread_t*)calloc(1, (size_t)nbThreads * sizeof(pthread_t));
if (ctx->threads == NULL) {
TPool_free(ctx);
return NULL;
}
ctx->threadCapacity = 0;
{ int i;
for (i = 0; i < nbThreads; ++i) {
if (pthread_create(&ctx->threads[i], NULL, &TPool_thread, ctx)) {
ctx->threadCapacity = (size_t)i;
TPool_free(ctx);
return NULL;
} }
ctx->threadCapacity = (size_t)nbThreads;
ctx->threadLimit = (size_t)nbThreads;
}
return ctx;
}
static void* TPool_thread(void* opaque) {
TPool* const ctx = (TPool*)opaque;
if (!ctx) { return NULL; }
for (;;) {
pthread_mutex_lock(&ctx->queueMutex);
while ( ctx->queueEmpty
|| (ctx->numThreadsBusy >= ctx->threadLimit) ) {
if (ctx->shutdown) {
pthread_mutex_unlock(&ctx->queueMutex);
return opaque;
}
pthread_cond_wait(&ctx->queuePopCond, &ctx->queueMutex);
}
{ TPool_job const job = ctx->queue[ctx->queueHead];
ctx->queueHead = (ctx->queueHead + 1) % ctx->queueSize;
ctx->numThreadsBusy++;
ctx->queueEmpty = (ctx->queueHead == ctx->queueTail);
pthread_cond_signal(&ctx->queuePushCond);
pthread_mutex_unlock(&ctx->queueMutex);
job.job_function(job.arg);
pthread_mutex_lock(&ctx->queueMutex);
ctx->numThreadsBusy--;
pthread_cond_signal(&ctx->queuePushCond);
pthread_mutex_unlock(&ctx->queueMutex);
}
}
assert(0);
}
static void TPool_shutdown(TPool* ctx) {
pthread_mutex_lock(&ctx->queueMutex);
ctx->shutdown = 1;
pthread_mutex_unlock(&ctx->queueMutex);
pthread_cond_broadcast(&ctx->queuePushCond);
pthread_cond_broadcast(&ctx->queuePopCond);
{ size_t i;
for (i = 0; i < ctx->threadCapacity; ++i) {
pthread_join(ctx->threads[i], NULL);
} }
}
void TPool_jobsCompleted(TPool* ctx){
pthread_mutex_lock(&ctx->queueMutex);
while(!ctx->queueEmpty || ctx->numThreadsBusy > 0) {
pthread_cond_wait(&ctx->queuePushCond, &ctx->queueMutex);
}
pthread_mutex_unlock(&ctx->queueMutex);
}
static int isQueueFull(TPool const* ctx) {
if (ctx->queueSize > 1) {
return ctx->queueHead == ((ctx->queueTail + 1) % ctx->queueSize);
} else {
return (ctx->numThreadsBusy == ctx->threadLimit) ||
!ctx->queueEmpty;
}
}
static void
TPool_submitJob_internal(TPool* ctx, void (*job_function)(void*), void *arg)
{
TPool_job job;
job.job_function = job_function;
job.arg = arg;
assert(ctx != NULL);
if (ctx->shutdown) return;
ctx->queueEmpty = 0;
ctx->queue[ctx->queueTail] = job;
ctx->queueTail = (ctx->queueTail + 1) % ctx->queueSize;
pthread_cond_signal(&ctx->queuePopCond);
}
void TPool_submitJob(TPool* ctx, void (*job_function)(void*), void* arg)
{
assert(ctx != NULL);
pthread_mutex_lock(&ctx->queueMutex);
while (isQueueFull(ctx) && (!ctx->shutdown)) {
pthread_cond_wait(&ctx->queuePushCond, &ctx->queueMutex);
}
TPool_submitJob_internal(ctx, job_function, arg);
pthread_mutex_unlock(&ctx->queueMutex);
}
#endif