213 lines
5.3 KiB
C
213 lines
5.3 KiB
C
/*
|
|
Fiber
|
|
|
|
Fiber is a way to manage concurrency at a more granular level than threads.
|
|
While threads represent a sequence of instructions that can run concurrently
|
|
with other threads,
|
|
a fiber is a unit of execution which only runs on its initiating thread.
|
|
Fibers are scheduled by the application, not the operating system.
|
|
They are great tools for implementing co-operative multitasking
|
|
where you have many tasks that you want to have run concurrently,
|
|
but you want to have explicit control on when they are switched in and out.
|
|
For server-side game development, fibres can be particularly useful in dealing
|
|
with multiple user requests, where each request might have its own fiber
|
|
*/
|
|
|
|
|
|
#include <pthread.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../type/queue.h"
|
|
|
|
#include <time.h>
|
|
|
|
#ifdef _WIN64
|
|
|
|
#include <windows.h>
|
|
|
|
void time_usleep(uint64_t usec) {
|
|
HANDLE timer;
|
|
LARGE_INTEGER ft;
|
|
|
|
ft.QuadPart = -(10*usec); // Convert to 100 nanosecond interval, negative value indicates relative time
|
|
|
|
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
|
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
|
|
WaitForSingleObject(timer, INFINITE);
|
|
CloseHandle(timer);
|
|
}
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
void time_usleep(uint64_t usec) {
|
|
usleep(usec);
|
|
}
|
|
|
|
#endif
|
|
|
|
typedef struct s_task {
|
|
void *(*func)(void *);
|
|
void *arg;
|
|
int id;
|
|
bool completed;
|
|
void *result;
|
|
} task_t;
|
|
|
|
static struct {
|
|
static_queue *async_tasks;
|
|
pthread_mutex_t async_mutex;
|
|
|
|
static_queue *sync_tasks;
|
|
pthread_mutex_t sync_mutex;
|
|
|
|
|
|
bool running;
|
|
pthread_mutex_t running_mtx;
|
|
|
|
int next_task_id;
|
|
static_queue *completed_tasks;
|
|
pthread_mutex_t status_mtx;
|
|
|
|
pthread_t workers[32];
|
|
} thread_mgr;
|
|
|
|
/*
|
|
worker function
|
|
need to be careful with waiting usleep could be too long or short
|
|
|
|
*/
|
|
static void *worker_function(void *arg) {
|
|
(void)arg;
|
|
|
|
bool run = true;
|
|
while (run) {
|
|
task_t task;
|
|
bool found_task = false;
|
|
|
|
pthread_mutex_lock(&thread_mgr.sync_mutex);
|
|
if (static_queue_pop(&thread_mgr.sync_tasks, &task) == 0) {
|
|
found_task = true;
|
|
}
|
|
pthread_mutex_unlock(&thread_mgr.sync_mutex);
|
|
|
|
if (!found_task) {
|
|
pthread_mutex_lock(&thread_mgr.async_mutex);
|
|
if (static_queue_pop(&thread_mgr.async_tasks, &task) == 0) {
|
|
found_task = true;
|
|
}
|
|
pthread_mutex_unlock(&thread_mgr.async_mutex);
|
|
}
|
|
|
|
if (found_task) {
|
|
task.result = task.func(task.arg);
|
|
|
|
pthread_mutex_lock(&thread_mgr.status_mtx);
|
|
static_queue_push(&thread_mgr.completed_tasks, &task.id);
|
|
pthread_mutex_unlock(&thread_mgr.status_mtx);
|
|
} else {
|
|
time_usleep(1000);
|
|
}
|
|
pthread_mutex_lock(&thread_mgr.running_mtx);
|
|
run = thread_mgr.running;
|
|
pthread_mutex_unlock(&thread_mgr.running_mtx);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
add task to pool, data inside arg should be used as input and output
|
|
*/
|
|
int thread_task_add(void *(*func)(void *), void *arg, bool is_synced) {
|
|
task_t task = {
|
|
.func = func,
|
|
.arg = arg,
|
|
.completed = false,
|
|
.result = NULL
|
|
};
|
|
|
|
pthread_mutex_lock(&thread_mgr.status_mtx);
|
|
task.id = thread_mgr.next_task_id++;
|
|
pthread_mutex_unlock(&thread_mgr.status_mtx);
|
|
|
|
if (is_synced) {
|
|
pthread_mutex_lock(&thread_mgr.sync_mutex);
|
|
static_queue_push(&thread_mgr.sync_tasks, &task);
|
|
pthread_mutex_unlock(&thread_mgr.sync_mutex);
|
|
} else {
|
|
pthread_mutex_lock(&thread_mgr.async_mutex);
|
|
static_queue_push(&thread_mgr.async_tasks, &task);
|
|
pthread_mutex_unlock(&thread_mgr.async_mutex);
|
|
}
|
|
|
|
return (task.id);
|
|
}
|
|
|
|
//not to rework as i just need to check if first data == id, then pop and return true else return false
|
|
bool thread_task_status(int task_id) {
|
|
pthread_mutex_lock(&thread_mgr.status_mtx);
|
|
|
|
int *completed_ids = thread_mgr.completed_tasks->data;
|
|
for (unsigned i = 0; i < thread_mgr.completed_tasks->size; i++) {
|
|
if (completed_ids[i] == task_id) {
|
|
memmove(&completed_ids[i], &completed_ids[i+1],
|
|
(thread_mgr.completed_tasks->size - i - 1) * sizeof(int));
|
|
thread_mgr.completed_tasks->size--;
|
|
// static_queue_pop(thread_mgr.completed_tasks, task_id);
|
|
pthread_mutex_unlock(&thread_mgr.status_mtx);
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&thread_mgr.status_mtx);
|
|
return (false);
|
|
}
|
|
|
|
/*
|
|
init thread_mgr and worker
|
|
*/
|
|
void thread_mgr_init() {
|
|
thread_mgr.async_tasks = static_queue_init(sizeof(task_t));
|
|
thread_mgr.sync_tasks = static_queue_init(sizeof(task_t));
|
|
thread_mgr.completed_tasks = static_queue_init(sizeof(int));
|
|
|
|
pthread_mutex_init(&thread_mgr.async_mutex, NULL);
|
|
pthread_mutex_init(&thread_mgr.sync_mutex, NULL);
|
|
pthread_mutex_init(&thread_mgr.status_mtx, NULL);
|
|
pthread_mutex_init(&thread_mgr.running_mtx, NULL);
|
|
|
|
thread_mgr.running = true;
|
|
thread_mgr.next_task_id = 1;
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
pthread_create(&thread_mgr.workers[i], NULL, worker_function, NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
raise stopping flag to all thread then wait for them to close
|
|
*/
|
|
void thread_mgr_close() {
|
|
pthread_mutex_lock(&thread_mgr.running_mtx);
|
|
thread_mgr.running = false;
|
|
pthread_mutex_unlock(&thread_mgr.running_mtx);
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
pthread_join(thread_mgr.workers[i], NULL);
|
|
}
|
|
|
|
static_queue_destroy(&thread_mgr.async_tasks);
|
|
static_queue_destroy(&thread_mgr.sync_tasks);
|
|
static_queue_destroy(&thread_mgr.completed_tasks);
|
|
|
|
pthread_mutex_destroy(&thread_mgr.async_mutex);
|
|
pthread_mutex_destroy(&thread_mgr.sync_mutex);
|
|
pthread_mutex_destroy(&thread_mgr.status_mtx);
|
|
pthread_mutex_destroy(&thread_mgr.running_mtx);
|
|
}
|