线程池笔记

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 线程池笔记

为什么需要线程池?

1 某类任务特别耗时,严重影响该线程处理其他任务。 这类任务在其他线程异步执行

2 线程资源的开销与CPU核心之间做平衡选择(固定线程的数量,避免在任务到来的时候开启线程,任务结束的时候又销毁线程)

作用: 1复用线程资源,2充分利用系统资源。 3(异步执行耗时任务)

线程池是一个生产消费模型。

构成:

1  生产者线程 : 发布任务

2  任务队列 :    

       任务 , 任务的上下文,任务的执行函数。

3  线程池(消费者线程):

        取出任务,执行任务,线程调度 mutex + condition(从无到有,从有到无)

4 平衡选择:

       耗时任务:

               IO密集型  : 2倍核心数+2    , CPU密集型: 与CPU核心数相同

               确定线程池线程数量:  (IO等待时间+CPU运算时间)X 核心数 / CPU运算时间

问题1:多个线程同时在一个任务队列里面取任务

    任务队列就相当于这多个线程的临界资源,取任务的时候需要对任务队列进行加锁。

问题2:当任务不足的时候,线程的状态(如何休息)

    条件变量(挂起) ---->进入条件等待,等待任务队列不为空的条件满足,然后唤醒线程。

实现一个线程池

1 创建线程池接口

               线程的数量,队列的长度

2 销毁线程池的接口

               标记线程池退出,通知所有线程

3 生产者抛出任务的接口

               构造任务 ,放入任务队列  , 通知线程唤醒

网络数据处理的阶段

1. 检测IO里面是否有数据(是否可读是否可写) IO事件是否就绪   EPOLL

2. 对IO进行读写操作。                                        RECV   \    SEND

3. 对数据进行解析。                                          parser解析

3种做法:

第一种:  单线程处理。

第二种:   可以把读写IO这两个任务抛到线程池里面。

第三种:   recv()之后,把解析完的(buffer)动作抛到线程池里面处理。

每一种各有优点和缺点

第一种: 如果在解析的过程当中引入了数据库的操作,整个过程会很慢。如果对于任务比较轻的纯内存操作式的,这种是OK的。

第二种: 针对于(fd)IO的操作时间比较长的,(比如说客户端的每一个fd设置成阻塞的IO)就可以采用这种方式。这种做法是三种做法里面速度最快的,不影响主循环。   但是,有一个很大的问题。     多个线程共用一个fd的现象  (客户端的发送请求间隔较短,服务端分配两个线程去处理同一个fd, 可能出现线程A在准备数据或者数据就绪的时候线程B已经把这个fd关闭了,还有可能AB两个线程同时发送,就会出现一些脏数据的现象。  

这三个步骤都可以分多线程进行。

任务队列是两个东西,首先是任务,其次才是队列。   任务先描述清楚,这个队列就是一个集合。

怎么把任务一个一个封装比较好  (写日记,计算,crud)。 首先每一个任务都不一样,这里我们需要一个回调函数,这个回调函数是由任务自己本身去实现的。  第二还有一个参数,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define LL_ADD(item, list) do {   \
  item->prev = NULL;        \
  item->next = list;        \
  list = item;          \
} while(0)
#define LL_REMOVE(item, list) do {            \
  if (item->prev != NULL) item->prev->next = item->next;  \
  if (item->next != NULL) item->next->prev = item->prev;  \
  if (list == item) list = item->next;          \
  item->prev = item->next = NULL;             \
} while(0)
typedef struct NWORKER {
  pthread_t thread;
  int terminate;
  struct NWORKQUEUE *workqueue;
  struct NWORKER *prev;
  struct NWORKER *next;
} nWorker;
typedef struct NJOB {
  void (*job_function)(struct NJOB *job);
  void *user_data;
  struct NJOB *prev;
  struct NJOB *next;
} nJob;
typedef struct NWORKQUEUE {
  struct NWORKER *workers;
  struct NJOB *waiting_jobs;
  pthread_mutex_t jobs_mtx;
  pthread_cond_t jobs_cond;
} nWorkQueue;
typedef nWorkQueue nThreadPool;
static void *ntyWorkerThread(void *ptr) {
  nWorker *worker = (nWorker*)ptr;
  while (1) {
    pthread_mutex_lock(&worker->workqueue->jobs_mtx);
    while (worker->workqueue->waiting_jobs == NULL) {
      if (worker->terminate) break;
      pthread_cond_wait(&worker->workqueue->jobs_cond, &worker->workqueue->jobs_mtx);
    }
    if (worker->terminate) {
      pthread_mutex_unlock(&worker->workqueue->jobs_mtx);
      break;
    }
    nJob *job = worker->workqueue->waiting_jobs;
    if (job != NULL) {
      LL_REMOVE(job, worker->workqueue->waiting_jobs);
    }
    pthread_mutex_unlock(&worker->workqueue->jobs_mtx);
    if (job == NULL) continue;
    job->job_function(job);
  }
  free(worker);
  pthread_exit(NULL);
}
int ntyThreadPoolCreate(nThreadPool *workqueue, int numWorkers) {
  if (numWorkers < 1) numWorkers = 1;
  memset(workqueue, 0, sizeof(nThreadPool));
  pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
  memcpy(&workqueue->jobs_cond, &blank_cond, sizeof(workqueue->jobs_cond));
  pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;
  memcpy(&workqueue->jobs_mtx, &blank_mutex, sizeof(workqueue->jobs_mtx));
  int i = 0;
  for (i = 0;i < numWorkers;i ++) {
    nWorker *worker = (nWorker*)malloc(sizeof(nWorker));
    if (worker == NULL) {
      perror("malloc");
      return 1;
    }
    memset(worker, 0, sizeof(nWorker));
    worker->workqueue = workqueue;
    int ret = pthread_create(&worker->thread, NULL, ntyWorkerThread, (void *)worker);
    if (ret) {
      perror("pthread_create");
      free(worker);
      return 1;
    }
    LL_ADD(worker, worker->workqueue->workers);
  }
  return 0;
}
void ntyThreadPoolShutdown(nThreadPool *workqueue) {
  nWorker *worker = NULL;
  for (worker = workqueue->workers;worker != NULL;worker = worker->next) {
    worker->terminate = 1;
  }
  pthread_mutex_lock(&workqueue->jobs_mtx);
  workqueue->workers = NULL;
  workqueue->waiting_jobs = NULL;
  pthread_cond_broadcast(&workqueue->jobs_cond);
  pthread_mutex_unlock(&workqueue->jobs_mtx);
}
void ntyThreadPoolQueue(nThreadPool *workqueue, nJob *job) {
  pthread_mutex_lock(&workqueue->jobs_mtx);
  LL_ADD(job, workqueue->waiting_jobs);
  pthread_cond_signal(&workqueue->jobs_cond);
  pthread_mutex_unlock(&workqueue->jobs_mtx);
}
/************************** debug thread pool **************************/
//sdk  --> software develop kit
// 提供SDK给其他开发者使用
#if 1
#define KING_MAX_THREAD     80
#define KING_COUNTER_SIZE   1000
void king_counter(nJob *job) {
  int index = *(int*)job->user_data;
  printf("index : %d, selfid : %lu\n", index, pthread_self());
  free(job->user_data);
  free(job);
}
int main(int argc, char *argv[]) {
  nThreadPool pool;
  ntyThreadPoolCreate(&pool, KING_MAX_THREAD);
  int i = 0;
  for (i = 0;i < KING_COUNTER_SIZE;i ++) {
    nJob *job = (nJob*)malloc(sizeof(nJob));
    if (job == NULL) {
      perror("malloc");
      exit(1);
    }
    job->job_function = king_counter;
    job->user_data = malloc(sizeof(int));
    *(int*)job->user_data = i;
    ntyThreadPoolQueue(&pool, job); 
  }
  getchar();
  printf("\n");
}
#endif

1. 多线程共用fd,如何避免多线程使用的脏数据?

2. 线程池的放缩策略,如何做比较好?

nginx 中线程池

作用:处理文件缓冲;

开启线程池

# 线程池默认关闭,configure 时,需要 --with-threads 来指定;

./configure --with-pcre-jit --with-http_ssl_module --with-http_realip_module --with-http_stub_status_module --with-http_v2_module -- with-threads

# 解决 gdb 远程权限的问题

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

# 创建名为 mark 的线程池

thread_pool mark threads=32 max_queue=65535;

location / {

root /img; aio threads=mark;

}

location / {

sendfile on; sendfile_max_chunk 128k; # 默认是没有限制的

}

location / {

directio 8m;

}

redis 中线程池

作用:读写 io 处理以及数据包解析、压缩;

redis当中的线程池,主线程既是生产者,也是消费者。且线程池的设计没有使用条件变量。主线程持有子线程的互斥锁,让子线程进入睡眠状态。

skynet 中线程池

作用:处理读写 io 、数据包解压缩、业务逻辑处理;特别地: 当同一个 io 在多个线程处理时,将写 io 转由网络线程处理;

线程池作用阶段

reactor将消息事件放入全局队列,线程池去全局队列消费。

目录
相关文章
|
机器学习/深度学习 缓存 Java
Python 线程,进程,多线程,多进程以及并行执行for循环笔记
Python 线程,进程,多线程,多进程以及并行执行for循环笔记
749 0
Python 线程,进程,多线程,多进程以及并行执行for循环笔记
|
6月前
|
设计模式 缓存 Java
谷粒商城笔记+踩坑(14)——异步和线程池
初始化线程的4种方式、线程池详解、异步编排 CompletableFuture
|
10月前
|
Java 测试技术 开发工具
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
|
10月前
|
存储 缓存 调度
FFmpeg开发笔记(十九)FFmpeg开启两个线程分别解码音视频
《FFmpeg开发实战》第10章示例playsync.c在处理音频流和视频流交错的文件时能实现同步播放,但对于分开存储的格式,会出现先播放全部声音再快速播放视频的问题。为解决此问题,需改造程序,增加音频处理线程和队列,以及相关锁,先将音视频帧读入缓存,再按时间戳播放。改造包括声明新变量、初始化线程和锁、修改数据包处理方式等。代码修改后在playsync2.c中,编译运行成功,控制台显示日志,SDL窗口播放视频并同步音频,证明改造有效。
163 0
FFmpeg开发笔记(十九)FFmpeg开启两个线程分别解码音视频
|
10月前
|
Java
线程池笔记
线程池笔记
59 0
|
Java 程序员 Windows
[笔记]Windows核心编程《十一》Windows线程池
[笔记]Windows核心编程《十一》Windows线程池
36766 4
[笔记]Windows核心编程《十一》Windows线程池
膜拜!清华大佬手撸多线程并发源码笔记Github上线3天星标35k+
你为什么要学习多线程?是因为理想吗?是因为热爱吗? 哦~原来是为了面试打基础、做准备啊!没错,这真的很现实!
膜拜!清华大佬手撸多线程并发源码笔记Github上线3天星标35k+
|
缓存 编译器 调度
[笔记]Windows核心编程《七》用户模式下的线程同步
[笔记]Windows核心编程《七》用户模式下的线程同步
101 1
|
缓存 安全 网络协议
[笔记]C++并发编程实战 《三》线程间共享数据(四)
[笔记]C++并发编程实战 《三》线程间共享数据(四)
|
存储 安全 算法
[笔记]C++并发编程实战 《三》线程间共享数据(三)
[笔记]C++并发编程实战 《三》线程间共享数据(三)
100 0