纯C语言实现线程池

1.介绍

        线程池是池式结构的一种,他的设计初衷是为了管理和重用一组初始化的对象或资源。这种模式在需要频繁创建和销毁大量相似对象或资源的应用场景下特别有用,这种池式结构的核心思想在于“重用”而非每次都进行“新建”和“销毁”,以此达到优化性能的目的。

2.实现线程池的目的

        在前面的文章UDP并发编程中,我们实现了一个基于UDP的echo server,后续又为他引入了kcp来确保数据传输的可靠,但是它是一个基于rector的单线程的echo server,他的性能上限非常低,今天来为它引入一个线程池,最后,我打算用纯C来实现这个线程池,虽然C++很好用,但我们的目的是为了学习,C++固然强大,但那股力量不属于你(狗头)。

3.线程池的结构

        文字太苍白先上图:

        整个线程池里包括任务队列与实际的工作线程 ,其中有一个巧妙的设计,我们把用于管理线程安全的互斥锁放入了任务队列中,而不是放入线程池或者实际的线程,我们明确了责任链,因为最终线程都是要去任务队列里拿任务消费,所以保障自己安全的东西应该由自己掌握,清晰的划分职责有利于后续扩展。

4.代码实现

        我们的线程最核心的部分就是任务队列,下面来拆解它:

        4.1任务队列的实现

        队列的特点是先进先出后进后出,所以我打算用链表来实现这一特性,当然用数组也可以,但是我觉得数组的长度不好规划,用链表更加简单,对于任务的push我们采用尾插,获取任务从链表头部获取,保证任务的有序。

        下面来定义这个任务队列:

#include <pthread.h>
#include "spinlock.h"

// 任务节点
typedef struct task_node {
    void* next;
} task;

// 任务队列
typedef struct task_queue {
    void* head;
    void** tail;
    
    //自旋锁
    spinlock lock; 
    //互斥锁
    pthread_mutex_t mutex;
    //条件变量
    pthread_cond_t cond;
} task_queue;

         链表的尾节点指针采用二级指针,这是为了方便我们拿到task的第一个元素,也就是next指针,如果对指针的运用不理解,我下个demo运行看结果你就明白了。     

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "spinlock.h"

// 任务节点
typedef struct task_node {
    void* next;
} task;

// 任务队列
typedef struct task_queue {
    void* head;
    void** tail;
    
    //自旋锁
    spinlock lock; 
    //互斥锁
    pthread_mutex_t mutex;
    //条件变量
    pthread_cond_t cond;
} task_queue;

int main() {
    char msg[] = "hello world";
    task* t = (task*)malloc(sizeof(task));

    void** p = (void**)t;
    *p = msg;
    printf("p = %p, t = %p, *p = %p, msg = %p\n", p, t, *p, msg);
    free(t);
    return 0;
}

        运行看结果:

        只要我们把task节点的指向下一个节点的next指针放在第一位,就可以这样用,他的原理通过C的内存布局和指针很好理解,多想想就能明白,这里就不细说,实在不理解评论留言,我们细说(狗头)。

         现在实现入队与出队以及任务队列初始化、销毁函数:

static inline void __add_task(task_queue *queue, void *task) {
    void **link = (void**)task;
    //因为是尾插,所以把只想下一个task的指针置为空
    *link = NULL;

    spinlock_lock(&queue->lock);
    *queue->tail  = link;
    queue->tail = link;
    spinlock_unlock(&queue->lock);
    //唤醒一个在休眠的线程
    pthread_cond_signal(&queue->cond);
}

static inline void* __pop_task(task_queue *queue) {
    spinlock_lock(&queue->lock);
    if (queue->head == NULL) {
        spinlock_unlock(&queue->lock);
        return NULL;
    }
    task *_task;
    _task = queue->head;
    
    //换头节点
    void **link = (void**)_task;
    queue->head = *link;
    //最后一个任务就把尾节点和有节点指在一起
    if (queue->head == NULL) {
        queue->tail = &queue->head;
    }
    spinlock_unlock(&queue->lock);
    return _task;
}

static task_queue* __taskqueue_create() {
    int ret;
    task_queue *queue = (task_queue *)malloc(sizeof(task_queue));
    if (queue) {
        ret = pthread_mutex_init(&queue->mutex, NULL);
        if (ret == 0) {
            ret = pthread_cond_init(&queue->cond, NULL);
            if (ret == 0) {
                spinlock_init(&queue->lock);
                queue->head = NULL;
                queue->tail = &queue->head;
                return queue;
            }
            pthread_mutex_destroy(&queue->mutex);
        }
        free(queue);
    }
    return NULL;
}

static void __nonblock(task_queue *queue) {
    pthread_mutex_lock(&queue->mutex);
    pthread_mutex_unlock(&queue->mutex);
    pthread_cond_broadcast(&queue->cond);
}

4.2线程创建于worker函数

        这就没什么好说的了,直接上代码:

#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>

// 任务节点
typedef struct task_node {
    void* next;
    handler_pt func;
    void *arg;
} task;

struct thread_pool {
    task_queue *task_queue;
    int thrd_count;
    pthread_t *threads;
    atomic_int quit;
};

static inline void* __get_task(task_queue *queue) {
    task *task;
    // while循环防止虚假唤醒
    while ((task = __pop_task(queue)) == NULL) {
        pthread_mutex_lock(&queue->mutex);
        pthread_cond_wait(&queue->cond, &queue->mutex);
        pthread_mutex_unlock(&queue->mutex);
    }
    return task;
}

static void* __thrdpool_worker(void *arg) {
    thread_pool *pool = (thread_pool*) arg;
    task *t;
    void *ctx;

    //原子变量,避免多线程下数据竞争
    while (atomic_load(&pool->quit) == 0) {
        t = (task*)__get_task(pool->task_queue);
        if (!t) break;
        handler_pt func = t->func;
        ctx = t->arg;
        free(t);
        func(ctx);
    }
    
    return NULL;
}

static void __threads_terminate(thread_pool * pool) {
    atomic_store(&pool->quit, 1);
    __nonblock(pool->task_queue);
    int i;
    for (i=0; i<pool->thrd_count; i++) {
        pthread_join(pool->threads[i], NULL);
    }
}

static int __threads_create(thread_pool *pool, size_t thrd_count) {
    pthread_attr_t attr;
	int ret;

    ret = pthread_attr_init(&attr);

    if (ret == 0) {
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thrd_count);
        if (pool->threads) {
            int i = 0;
            for (; i < thrd_count; i++) {
                if (pthread_create(&pool->threads[i], &attr, __thrdpool_worker, pool) != 0) {
                    break;
                }
            }
            pool->thrd_count = i;
            pthread_attr_destroy(&attr);
            if (i == thrd_count)
                return 0;
            __threads_terminate(pool);
            free(pool->threads);
        }
        ret = -1;
    }
    return ret; 
}

4.3对外接口

        现在实现对外的thread_pool.h文件:

#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__

typedef struct thread_pool thread_pool;

typedef void (*handler_pt)(void * arg);

#ifdef __cplusplus
extern "C"
{
#endif

thread_pool* thread_pool_create(int thrd_count);

void thread_pool_terminate(thread_pool * pool);

//等待线程退出
void thread_pool_waitdone(thread_pool *pool);

int thread_pool_post(thread_pool *pool, handler_pt func, void *arg);

#ifdef __cplusplus
}
#endif

#endif

        对应具体实现:

void thread_pool_terminate(thread_pool * pool) {
    atomic_store(&pool->quit, 1);
    __nonblock(pool->task_queue);
}

thread_pool* thread_pool_create(int thrd_count) {
    thread_pool *pool;

    pool = (thread_pool*)malloc(sizeof(*pool));
    if (pool) {
        task_queue *queue = __taskqueue_create();
        if (queue) {
            pool->task_queue = queue;
            atomic_init(&pool->quit, 0);
            if (__threads_create(pool, thrd_count) == 0)
                return pool;
            __taskqueue_destroy(queue);
        }
        free(pool);
    }
    return NULL;
}

int thread_pool_post(thread_pool *pool, handler_pt func, void *arg) {
    if (atomic_load(&pool->quit) == 1) 
        return -1;
    task *t = (task*) malloc(sizeof(task));
    if (!t) return -1;
    t->func = func;
    t->arg = arg;
    __add_task(pool->task_queue, t);
    return 0;
}

void thread_pool_waitdone(thread_pool *pool) {
    int i;
    for (i=0; i<pool->thrd_count; i++) {
        pthread_join(pool->threads[i], NULL);
    }
    __taskqueue_destroy(pool->task_queue);
    free(pool->threads);
    free(pool);
}

4.4自旋锁与原子变量 

         为了确保多线程下避免数据竞争问题,我们引入了自旋锁,以下是他的实现,文件spinlock.h

#ifndef SKYNET_SPINLOCK_H
#define SKYNET_SPINLOCK_H

#define SPIN_INIT(q) spinlock_init(&(q)->lock);
#define SPIN_LOCK(q) spinlock_lock(&(q)->lock);
#define SPIN_UNLOCK(q) spinlock_unlock(&(q)->lock);
#define SPIN_DESTROY(q) spinlock_destroy(&(q)->lock);

#ifndef USE_PTHREAD_LOCK

#include "atomic.h"

#define atomic_test_and_set_(ptr) STD_ atomic_exchange_explicit(ptr, 1, STD_ memory_order_acquire)
#define atomic_clear_(ptr) STD_ atomic_store_explicit(ptr, 0, STD_ memory_order_release);
#define atomic_load_relaxed_(ptr) STD_ atomic_load_explicit(ptr, STD_ memory_order_relaxed)

#if defined(__x86_64__)
#include <immintrin.h> // For _mm_pause
#define atomic_pause_() _mm_pause()
#else
#define atomic_pause_() ((void)0)
#endif

typedef struct spinlock {
	STD_ atomic_int lock;
} spinlock;

static inline void
spinlock_init(struct spinlock *lock) {
	STD_ atomic_init(&lock->lock, 0);
}

static inline void
spinlock_lock(struct spinlock *lock) {
	for (;;) {
		if (!atomic_test_and_set_(&lock->lock))
			return;
		while (atomic_load_relaxed_(&lock->lock))
			atomic_pause_();
	}
}

static inline int
spinlock_trylock(struct spinlock *lock) {
	return !atomic_load_relaxed_(&lock->lock) &&
		!atomic_test_and_set_(&lock->lock);
}

static inline void
spinlock_unlock(struct spinlock *lock) {
	atomic_clear_(&lock->lock);
}

static inline void
spinlock_destroy(struct spinlock *lock) {
	(void) lock;
}


#endif

上述自旋锁的实现原理是基于原子变量,以下是它的一下封装,文件atomic.h:

#ifndef SKYNET_ATOMIC_H
#define SKYNET_ATOMIC_H


#if defined (__cplusplus)
#include <atomic>
#define STD_ std::
#define atomic_value_type_(p, v) decltype((p)->load())(v) 
#else
#include <stdatomic.h>
#define STD_
#define atomic_value_type_(p, v) v
#endif

#define ATOM_INT  STD_ atomic_int
#define ATOM_POINTER STD_ atomic_uintptr_t
#define ATOM_SIZET STD_ atomic_size_t
#define ATOM_ULONG STD_ atomic_ulong
#define ATOM_INIT(ref, v) STD_ atomic_init(ref, v)
#define ATOM_LOAD(ptr) STD_ atomic_load(ptr)
#define ATOM_STORE(ptr, v) STD_ atomic_store(ptr, v)

static inline int
ATOM_CAS(STD_ atomic_int *ptr, int oval, int nval) {
	return STD_ atomic_compare_exchange_weak(ptr, &(oval), nval);
}

static inline int
ATOM_CAS_SIZET(STD_ atomic_size_t *ptr, size_t oval, size_t nval) {
	return STD_ atomic_compare_exchange_weak(ptr, &(oval), nval);
}

static inline int
ATOM_CAS_ULONG(STD_ atomic_ulong *ptr, unsigned long oval, unsigned long nval) {
	return STD_ atomic_compare_exchange_weak(ptr, &(oval), nval);
}

static inline int
ATOM_CAS_POINTER(STD_ atomic_uintptr_t *ptr, uintptr_t oval, uintptr_t nval) {
	return STD_ atomic_compare_exchange_weak(ptr, &(oval), nval);
}

#define ATOM_FINC(ptr) STD_ atomic_fetch_add(ptr, atomic_value_type_(ptr,1))
#define ATOM_FDEC(ptr) STD_ atomic_fetch_sub(ptr, atomic_value_type_(ptr, 1))
#define ATOM_FADD(ptr,n) STD_ atomic_fetch_add(ptr, atomic_value_type_(ptr, n))
#define ATOM_FSUB(ptr,n) STD_ atomic_fetch_sub(ptr, atomic_value_type_(ptr, n))
#define ATOM_FAND(ptr,n) STD_ atomic_fetch_and(ptr, atomic_value_type_(ptr, n))

#endif


#endif

         最后,尝试编译成动态库。

         ok,成功没有编译问题。

        参考学习:0voice · GitHub 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值