目录
线程池介绍
①线程池定义:
维持和管理固定数量线程的结构,用于解决资源频繁创建和销毁的问题。
②线程池组成:
固定数量的线程、队列、任务状态管理。
④线程池的作用:
避免频繁创建和销毁线程,管理线程状态,提高系统资源利用率。
1.线程池的组成部分
①线程:
固定数量的线程用于处理任务。
②队列:
用于管理任务,通常使用队列数据结构。
③锁:
用于保护队列的数据安全,防止并发访问冲突。
2.线程池的线程数量确定
①CPU密集型任务:
线程数量与CPU核心数相同。
②IO密集型任务:
线程数量通常为两倍CPU核心数加二。
③经验公式:
通过测试确定最佳线程数量。
3.线程池的应用场景
①.性能优化:
优化耗时计算和IO操作,提高吞吐量。
②.网络IO优化:
使用线程池并发处理网络数据。
③.框架应用:
在开源框架和工作中使用线程池。
4.线程池的工作原理
①.生产消费模型:
生产者线程抛出任务到队列,消费者线程从队列中取出任务执行。
②.任务状态管理:
队列状态决定线程状态,队列为空时线程休眠,有任务时线程活跃。
③.异步任务:
任务在消费者线程中异步执行,不占用生产者线程。
5.线程池的接口设计
①.接口设计:
暴露精简的接口给用户使用,隐藏具体实现细节。
②.任务执行规范:
定义任务执行的函数指针和上下文参数。
③.线程池对象:
用于标识和管理线程池。
6.线程池的数据结构设计
①.队列设计:
使用双开口队列数据结构,支持追加和弹出操作。
②.原子变量:
用于标记线程池的退出状态,保证线程安全。
③.线程计数器:
存储线程的PID,用于管理和关闭线程。
补充::
7.具体线程池的代码实现如下
thrd_pool.c
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include "thrd_pool.h"
#include "spinlock.h"
/**
* shell: gcc thrd_pool.c -c -fPIC
* shell: gcc -shared thrd_pool.o -o libthrd_pool.so -I./ -L./ -lpthread
* usage: include thrd_pool.h & link libthrd_pool.so
*/
typedef struct spinlock spinlock_t;//自旋锁
typedef struct task_s {
void *next;
handler_pt func;
void *arg;
} task_t;
typedef struct task_queue_s {
void *head;
void **tail;
int block;//阻塞标志
spinlock_t lock;
pthread_mutex_t mutex;
pthread_cond_t cond;//条件变量
} task_queue_t;
struct thrdpool_s {
task_queue_t *task_queue;
atomic_int quit;// 退出标志
int thrd_count;
pthread_t *threads; // 线程数组,存储线程池中的线程
};
// 对称
// 资源的创建 回滚式编程
// 业务逻辑 防御式编程
static task_queue_t *
__taskqueue_create() {
int ret;
task_queue_t *queue = (task_queue_t *)malloc(sizeof(task_queue_t));
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;
queue->block = 1;
return queue;
}
pthread_mutex_destroy(&queue->mutex);
}
free(queue);
}
return NULL;
}
static void
__nonblock(task_queue_t *queue) {
pthread_mutex_lock(&queue->mutex);
queue->block = 0;//。将其设置为 0,表示队列不再处于阻塞状态,线程可以继续执行。
pthread_mutex_unlock(&queue->mutex);
pthread_cond_broadcast(&queue->cond);
}
static inline void
__add_task(task_queue_t *queue, void *task) {
// 不限定任务类型,只要该任务的结构起始内存是一个用于链接下一个节点的指针
void **link = (void**)task;
*link = NULL;//task->next=null
spinlock_lock(&queue->lock);
*queue->tail /* 等价于 queue->tail->next */ = link;
queue->tail = link;
spinlock_unlock(&queue->lock);
pthread_cond_signal(&queue->cond);//这行代码通过 pthread_cond_signal 唤醒一个因条件变量 queue->cond 而阻塞的线程。
}
static inline void *
__pop_task(task_queue_t *queue) {
spinlock_lock(&queue->lock);
if (queue->head == NULL) {
spinlock_unlock(&queue->lock);
return NULL;
}
task_t *task;
task = queue->head;
void **link = (void**)task;
queue->head = *link;//更新队列的头指针 queue->head,使其指向当前任务的 next 指针(即 *link)。
if (queue->head == NULL) {
queue->tail = &queue->head;
}
spinlock_unlock(&queue->lock);
return task;
}
static inline void *
__get_task(task_queue_t *queue) {
task_t *task;
// 虚假唤醒
while ((task = __pop_task(queue)) == NULL) {
pthread_mutex_lock(&queue->mutex);
if (queue->block == 0) {
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
// 1. 先 unlock(&mtx)
// 2. 在 cond 休眠
// --- __add_task 时唤醒
// 3. 在 cond 唤醒
// 4. 加上 lock(&mtx);
pthread_cond_wait(&queue->cond, &queue->mutex);
pthread_mutex_unlock(&queue->mutex);
}
return task;
}
static void
__taskqueue_destroy(task_queue_t *queue) {
task_t *task;
while ((task = __pop_task(queue))) {
free(task);
}
spinlock_destroy(&queue->lock);
pthread_cond_destroy(&queue->cond);
pthread_mutex_destroy(&queue->mutex);
free(queue);
}
static void *
__thrdpool_worker(void *arg) {
thrdpool_t *pool = (thrdpool_t*) arg;
task_t *task;
void *ctx;
while (atomic_load(&pool->quit) == 0) {
task = (task_t*)__get_task(pool->task_queue);
if (!task) break;
handler_pt func = task->func;
ctx = task->arg;
free(task);
func(ctx);
}
return NULL;
}
static void
__threads_terminate(thrdpool_t * 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(thrdpool_t *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;
}
void
thrdpool_terminate(thrdpool_t * pool) {
atomic_store(&pool->quit, 1);
__nonblock(pool->task_queue);
}
thrdpool_t *
thrdpool_create(int thrd_count) {
thrdpool_t *pool;
pool = (thrdpool_t*)malloc(sizeof(*pool));
if (pool) {
task_queue_t *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
thrdpool_post(thrdpool_t *pool, handler_pt func, void *arg) {
if (atomic_load(&pool->quit) == 1)
return -1;
task_t *task = (task_t*) malloc(sizeof(task_t));
if (!task) return -1;
task->func = func;
task->arg = arg;
__add_task(pool->task_queue, task);
return 0;
}
void
thrdpool_waitdone(thrdpool_t *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);
}
thrd_pool.h
#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H
typedef struct thrdpool_s thrdpool_t;
// 任务执行的规范 ctx 上下文
typedef void (*handler_pt)(void * /* ctx */);
#ifdef __cplusplus
extern "C"
{
#endif
// 对称处理
thrdpool_t *thrdpool_create(int thrd_count);
void thrdpool_terminate(thrdpool_t * pool);
int thrdpool_post(thrdpool_t *pool, handler_pt func, void *arg);
void thrdpool_waitdone(thrdpool_t *pool);
#ifdef __cplusplus
}
#endif
#endif
内存池的介绍
内存池是一种内存管理技术,以下是其简单介绍:
1.内存池的定义
内存池是在程序运行开始时,预先分配一块较大的内存空间作为 “池”,当程序需要申请内存时,直接从这个池中分配小块内存给用户,而不是每次都向操作系统申请;当用户释放内存时,也不是直接归还给操作系统,而是将其放回内存池,以便后续再次使用。
2.内存池的作用
提高内存分配效率:避免了频繁地向操作系统申请和释放内存所带来的开销。因为操作系统进行内存分配的操作相对复杂,涉及到查找合适的内存块、维护内存管理数据结构等,而从内存池中分配内存则可以快速定位到可用的内存块,大大提高了分配速度。
减少内存碎片:连续的内存分配和释放容易导致内存碎片的产生,即内存中存在许多不连续的小空闲块,无法满足较大内存分配的需求。内存池通过合理的管理策略,尽量减少这种碎片的产生,提高内存的利用率。
3.内存池的实现原理
内存块划分:内存池通常会将预先分配的大内存空间划分为不同大小的内存块,以满足不同大小的内存分配请求。这些内存块可以通过链表等数据结构进行组织,方便快速查找和分配。
分配策略:当有内存分配请求时,内存池会根据请求的大小,在相应大小的内存块链表中查找可用的内存块。如果找到,则直接返回该内存块给用户;如果没有找到合适的内存块,可能会根据具体的策略进行一些处理,如尝试从其他大小的内存块中进行拆分,或者申请新的内存空间来扩充内存池。
回收策略:当用户释放内存时,内存池会将释放的内存块标记为可用,并将其放回相应的内存块链表中。有些内存池还会采用一些优化策略,如在一定条件下将相邻的空闲内存块合并成更大的内存块,以减少内存碎片。
4.内存池的应用场景
频繁内存分配的场景:如网络服务器中处理大量的连接请求,每次请求可能都需要分配一些内存来存储相关的数据,如果没有内存池,频繁的内存分配和释放会导致性能下降。使用内存池可以提高服务器的响应速度和稳定性。
实时性要求较高的系统:如嵌入式系统中的实时操作系统,对内存分配的时间有严格要求。内存池可以保证在短时间内快速分配内存,满足实时任务的需求。
在kv存储文章的末尾有具体实现了一种简单的内存池
mysql连接池
1.课程介绍和池化技术概述
①.介绍了课程内容和目标,重点讲解了池化技术的概念及其重要性。
②.池化技术主要用于减少资源创建和销毁的次数,从而提高程序的响应性能,尤其是在高并发场景下。
③.线程池和内存池是常见的池化技术,它们通过复用资源来提升性能。
2.数据库连接池的概念和作用
①.数据库连接池是在程序启动时创建足够的数据库连接,并组成一个连接池。
②.连接池会根据程序需要动态申请、使用和释放连接。
③.数据库连接池可以提升数据库访问的性能,尤其是在高并发处理SQL指令时
3.数据库连接的定义和特性
①.数据库连接是指服务器与数据库之间的连接,通常是通过TCP连接实现的。
②.TCP连接需要经过三次握手建立连接,并通过四次挥手断开连接。
③.数据库连接是一种长连接,需要维护连接的生命周期。
4.请求回应模式和数据库访问模式
①.请求回应模式是服务器与数据库之间的交互模式,服务器主动请求数据并等待数据库的响应。
②.数据库不会主动推送数据给服务器,所有数据交互都是通过请求和回应完成的。
③.这种模式适用于所有基于访问模式的数据库操作。
5.高并发处理和MySQL的网络模型
①.高并发处理是指数据库在处理大量并发请求时的性能表现。
②.MySQL采用IO多路复用的方式来处理并发连接,主要通过select函数监听连接。
③.MySQL会为每条连接分配一个线程,并在线程中处理SQL语句。
6.数据库连接池的限制和考虑因素
①.数据库连接池的大小是受限的,受操作系统资源限制和MySQL的处理能力限制。
②.连接池的大小需要根据实际需求和系统资源进行调整。
③.长连接和短连接的选择取决于具体的应用场景和需求。
7.长连接和短连接的区别
①.长连接是指维持一条连接的活跃状态,复用连接执行SQL语句。
②.短连接是执行SQL语句后立即断开连接,不复用连接。
③.长连接适用于高并发场景,短连接适用于低并发场景。
8.TCP连接的收发数据和MySQL协议
①.TCP连接的收发数据需要遵循MySQL协议进行编码和解码。
②.MySQL驱动需要具备编码和解码MySQL协议的能力。
③.MySQL驱动可以通过官方提供或自定义实现。
9.阻塞IO和非阻塞IO的区别
①.阻塞IO是指IO函数在IO未就绪时会阻塞线程,导致线程无法继续执行其他任务。
②.非阻塞IO则不会阻塞线程,允许线程在等待IO时继续处理其他任务。
③.高性能服务器框架通常使用非阻塞IO。
10.同步连接池和异步连接池的区别
①.同步连接池是指使用同步连接方式访问数据库,会阻塞当前线程等待数据库返回结果。
②.异步连接池使用异步连接方式访问数据库,不会阻塞当前线程,通过另起线程或异步事件处理结果。
③.异步连接池的性能通常高于同步连接池。
11.同步连接的原理和应用场景
①.同步连接的原理是阻塞当前线程等待数据库返回结果。
②.同步连接适用于服务器启动时的资源初始化等场景。
③.同步连接的优点是简单直观,缺点是会阻塞线程。
12.异步连接的原理和应用场景
①.异步连接的原理是另起线程等待数据库结果,或通过异步事件处理结果。
②.异步连接适用于服务器运行过程中的业务处理场景。
③.异步连接的优点是释放主线程的处理能力,缺点是复杂度较高。