30天自制C++服务器day11-完善线程池,加入一个简单的测试程序

29 篇文章 3 订阅
19 篇文章 5 订阅

在昨天的教程里,我们添加了一个最简单的线程池到服务器,一个完整的Reactor模式正式成型。这个线程池只是为了满足我们的需要构建出的最简单的线程池,存在很多问题。比如,由于任务队列的添加、取出都存在拷贝操作,线程池不会有太好的性能,只能用来学习,正确做法是使用右值移动、完美转发等阻止拷贝。另外线程池只能接受std::function<void()>类型的参数,所以函数参数需要事先使用std::bind(),并且无法得到返回值。

为了解决以上提到的问题,线程池的构造函数和析构函数都不会有太大变化,唯一需要改变的是将任务添加到任务队列的add函数。我们希望使用add函数前不需要手动绑定参数,而是直接传递,并且可以得到任务的返回值。新的实现代码如下:

template<class F, class... Args>
auto ThreadPool::add(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;  //返回值类型

    auto task = std::make_shared< std::packaged_task<return_type()> >(  //使用智能指针
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)  //完美转发参数
        );  
        
    std::future<return_type> res = task->get_future();  // 使用期约
    {   //队列锁作用域
        std::unique_lock<std::mutex> lock(tasks_mtx);   //加锁

        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });  //将任务添加到任务队列
    }
    cv.notify_one();    //通知一次条件变量
    return res;     //返回一个期约
}

这里使用了大量C++11之后的新标准,具体使用方法可以参考欧长坤《现代 C++ 教程》。另外这里使用了模版,所以不能放在cpp文件,因为C++编译器不支持模版的分离编译

这是一个复杂的问题,具体细节请参考《深入理解计算机系统》有关编译、链接的章节

此外,我们希望对现在的服务器进行多线程、高并发的测试,所以需要使用网络库写一个简单的多线程高并发测试程序,具体实现请参考源代码,使用方式如下:

./test -t 10000 -m 10 (-w 100)
# 10000个线程,每个线程回显10次,建立连接后等待100秒开始发送消息(可用于测试服务器能同时保持的最大连接数)。不指定w参数,则建立连接后开始马上发送消息。

注意Makefile文件也已重写,现在使用make只能编译服务器,客户端、测试程序的编译指令请参考Makefile文件,服务器程序编译后可以使用vscode调试。也可以使用gdb调试:

gdb server  #使用gdb调试
r           #执行
where / bt  #查看调用栈

今天还发现了之前版本的一个缺点:对于Acceptor,接受连接的处理时间较短、报文数据极小,并且一般不会有特别多的新连接在同一时间到达,所以Acceptor没有必要采用epoll ET模式,也没有必要用线程池。由于不会成为性能瓶颈,为了简单最好使用阻塞式socket,故今天的源代码中做了以下改变:

  1. Acceptor socket fd(服务器监听socket)使用阻塞式
  2. Acceptor使用LT模式,建立好连接后处理事件fd读写用ET模式
  3. Acceptor建立连接不使用线程池,建立好连接后处理事件用线程池

至此,今天的教程已经结束了。使用测试程序来测试我们的服务器,可以发现并发轻松上万。这种设计架构最容易想到、也最容易实现,但有很多缺点,具体请参考陈硕《Linux多线程服务器编程》第三章,在明天的教程中将使用one loop per thread模式改写。

此外,多线程系统编程是一件极其复杂的事情,比此教程中的设计复杂得多,由于这是入门教程,故不会涉及到太多细节,作者也还没有水平讲好这个问题。但要想成为一名合格的C++程序员,高并发编程是必备技能,还需要年复一年地阅读大量书籍、进行大量实践。

路漫漫其修远兮,吾将上下而求索 ———屈原《离骚》

完整源代码:https://github.com/yuesong-feng/30dayMakeCppServer/tree/main/code/day11

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我了解了你的问题。以下是一个基本的线程池实现示例,附带注释。 ```c #include <pthread.h> #define THREADPOOL_MAX_THREADS 50 #define THREADPOOL_MAX_QUEUE 1000 // 任务的结构体 typedef struct { void (*function)(void *); // 任务函数指针 void *argument; // 任务函数的参数 } threadpool_task_t; // 线程池的结构体 struct threadpool_t { pthread_mutex_t lock; // 互斥锁,用于线程池的同步 pthread_cond_t notify; // 线程条件变量,用于通知等待任务的线程 pthread_t *threads; // 线程数组,存储所有的线程 ID threadpool_task_t *queue; // 任务队列 int thread_count; // 线程总数 int queue_size; // 队列的当前长度 int head; // 任务队列的队头索引 int tail; // 任务队列的队尾索引 int shutdown; // 标志位,表示线程池是否已经关闭 int started; // 标志位,表示线程池是否已经启动 }; // 初始化线程池 threadpool_t *threadpool_create(int thread_count, int queue_size) { // 不要让线程数或队列大小为 0 if (thread_count <= 0 || thread_count > THREADPOOL_MAX_THREADS || queue_size <= 0 || queue_size > THREADPOOL_MAX_QUEUE) { return NULL; } threadpool_t *pool; int i; // 创建线程池结构体 if ((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) { return NULL; } // 初始化线程池变量 pool->thread_count = 0; pool->queue_size = queue_size; pool->head = pool->tail = pool->queue_size = 0; pool->shutdown = pool->started = 0; // 分配线程和任务队列的空间 pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count); pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size); // 初始化互斥锁和线程条件变量 if ((pthread_mutex_init(&(pool->lock), NULL) != 0) || (pthread_cond_init(&(pool->notify), NULL) != 0)) { threadpool_destroy(pool); return NULL; } // 创建线程 for (i = 0; i < thread_count; i++) { if (pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool) != 0) { threadpool_destroy(pool); return NULL; } pool->thread_count++; pool->started++; } return pool; } // 添加任务到线程池的任务队列 int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument) { // 确定下一个任务的索引 int next; if (pool == NULL || function == NULL) { return -1; } if (pthread_mutex_lock(&(pool->lock)) != 0) { return -1; } // 计算下一个队列索引 next = pool->tail + 1; if (next == pool->queue_size) { next = 0; } // 队列已满,添加任务失败 if (pool->queue_size == pool->queue_size) { pthread_mutex_unlock(&(pool->lock)); return -1; } // 将新任务添加到任务队列 pool->queue[pool->tail].function = function; pool->queue[pool->tail].argument = argument; pool->tail = next; pool->queue_size++; // 通知等待的线程有新任务到来 if (pthread_cond_signal(&(pool->notify)) != 0) { pthread_mutex_unlock(&(pool->lock)); return -1; } pthread_mutex_unlock(&(pool->lock)); return 0; } // 线程池的线程函数 void *threadpool_thread(void *threadpool) { threadpool_t *pool = (threadpool_t *)threadpool; threadpool_task_t task; for (;;) { // 等待新任务到来 pthread_mutex_lock(&(pool->lock)); // 如果没有新任务,并且线程池被关闭了,就结束该线程 while (pool->queue_size == 0 && !pool->shutdown) { pthread_cond_wait(&(pool->notify), &(pool->lock)); } // 如果线程池被关闭并且没有新任务了,就结束该线程 if (pool->shutdown && pool->queue_size == 0) { break; } // 获取队头任务 task.function = pool->queue[pool->head].function; task.argument = pool->queue[pool->head].argument; pool->head += 1; pool->queue_size -= 1; // 计算下一个队列索引 if (pool->head == pool->queue_size) { pool->head = 0; } // 通知其他线程有新任务可以执行 pthread_mutex_unlock(&(pool->lock)); // 执行任务函数 (*(task.function))(task.argument); } // 退出线程 pool->started--; pthread_mutex_unlock(&(pool->lock)); pthread_exit(NULL); return NULL; } // 关闭线程池 int threadpool_destroy(threadpool_t *pool) { int i; if (pool == NULL) { return -1; } if (pthread_mutex_lock(&(pool->lock)) != 0) { return -1; } // 如果线程池已经被关闭了,就返回 -1 if (pool->shutdown) { pthread_mutex_unlock(&(pool->lock)); return -1; } // 标志线程池要关闭 pool->shutdown = 1; // 唤醒所有等待的线程 if (pthread_cond_broadcast(&(pool->notify)) != 0) { pthread_mutex_unlock(&(pool->lock)); return -1; } pthread_mutex_unlock(&(pool->lock)); // 等待正在执行的线程结束 for (i = 0; i < pool->thread_count; i++) { if (pthread_join(pool->threads[i], NULL) != 0) { return -1; } } // 释放线程和任务队列的空间 free(pool->threads); free(pool->queue); // 销毁互斥锁和线程条件变量 pthread_mutex_destroy(&(pool->lock)); pthread_cond_destroy(&(pool->notify)); free(pool); return 0; } ``` 希望我这个示例能够帮到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值