线程池/内存池/mysql连接池

目录

线程池介绍

1.线程池的组成部分

2.线程池的线程数量确定

3.线程池的应用场景

4.线程池的工作原理

5.线程池的接口设计

6.线程池的数据结构设计

7.具体线程池的代码实现如下

内存池的介绍

1.内存池的定义

2.内存池的作用

3.内存池的实现原理

4.内存池的应用场景

mysql连接池

1.课程介绍和池化技术概述

2.数据库连接池的概念和作用

3.数据库连接的定义和特性

4.请求回应模式和数据库访问模式

5.高并发处理和MySQL的网络模型

6.数据库连接池的限制和考虑因素

7.长连接和短连接的区别

8.TCP连接的收发数据和MySQL协议

9.阻塞IO和非阻塞IO的区别

10.同步连接池和异步连接池的区别

11.同步连接的原理和应用场景

12.异步连接的原理和应用场景


线程池介绍

①线程池定义:

维持和管理固定数量线程的结构,用于解决资源频繁创建和销毁的问题。

②线程池组成:

固定数量的线程、队列、任务状态管理。

④线程池的作用:

避免频繁创建和销毁线程,管理线程状态,提高系统资源利用率。

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.异步连接的原理和应用场景

①.异步连接的原理是另起线程等待数据库结果,或通过异步事件处理结果。

②.异步连接适用于服务器运行过程中的业务处理场景。

③.异步连接的优点是释放主线程的处理能力,缺点是复杂度较高。

猜你喜欢

转载自blog.csdn.net/weixin_71694051/article/details/147030762
今日推荐