池化技術
池化技術能夠減少資源對象的創建次數,提?程序的響應性能,特別是在?并發下這種提?更加明顯。使用池化技術緩存的資源對象有如下共同特點:
- 對象創建時間長;
- 對象創建需要大量資源;
- 對象創建后可被重復使用像常見的線程池、內存池、連接池、對象池都具有以上的共同特點。
連接池
什么是數據庫連接池
定義:數據庫連接池(Connection pooling)是程序啟動時建立足夠的數據庫連接,并將這些連接組成一個連接池,由程序動態地對池中的連接進行申請,使用,釋放。
大白話:創建數據庫連接是?個很耗時的操作,也容易對數據庫造成安全隱患。所以,在程序初始化的時候,集中創建多個數據庫連接,并把他們集中管理,供程序使用,可以保證較快的數據庫讀寫速度,還更加安全可靠。這里講的數據庫,不單只是指Mysql,也同樣適用于Redis。
為什么使用數據庫連接池
- 資源復用:由于數據庫連接得到復用,避免了頻繁的創建、釋放連接引起的性能開銷,在減少系統消耗的基礎上,另一方面也增進了系統運行環境的平穩性(減少內存碎片以及數據庫臨時進程/線程的數量)。
- 更快的系統響應速度:數據庫連接池在初始化過程中,往往已經創建了若干數據庫連接置于池中備用。此時連接的初始化工作均已完成。對于業務請求處理而言,直接利用現有可用連接,避免了從數據庫連接初始化和釋放過程的開銷,從而縮減了系統整體響應時間。
- 統?的連接管理:避免數據庫連接泄露,在較為完備的數據庫連接池實現中,可根據預先的連接占用超時設定,強制收回被占用連接。從而避免了常規數據庫連接操作中可能出現的資源泄露。
如果不使用連接池
- TCP建立連接的三次握手(客戶端與MySQL服務器的連接基于TCP協議)
- MySQL認證的三次握手
- 真正的SQL執行
- MySQL的關閉
- TCP的四次握手關閉
可以看到,為了執行?條SQL,需要進行TCP三次握手,Mysql認證、Mysql關閉、TCP四次揮手等其他操作,執行SQL操作在所有的操作占比非常低。
優點:實現簡單
缺點:
需要C/C++ Linux服務器架構師學習資料加qun812855908獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享
使用連接池
第?次訪問的時候,需要建立連接。但是之后的訪問,均會復用之前創建的連接,直接執行SQL語句。
優點:
- 降低了網絡開銷
- 連接復用,有效減少連接數。
- 提升性能,避免頻繁的新建連接。新建連接的開銷比較大
- 沒有TIME_WAIT狀態的問題
缺點:
- 設計較為復雜
長連接和連接池的區別
- 長連接是?些驅動、驅動框架、ORM工具的特性,由驅動來保持連接句柄的打開,以便后續的數據庫操作可以重用連接,從而減少數據庫的連接開銷。
- 而連接池是應用服務器的組件,它可以通過參數來配置連接數、連接檢測、連接的生命周期等。
- 連接池內的連接,其實就是長連接。
數據庫連接池運行機制
- 用戶發送請求,把請求插入到消息隊列
- 線程池中的線程競爭從消息隊列拿出任務(涉及多線程競爭,加鎖)
- 線程從連接池獲取或創建可用連接(涉及多線程競爭,加鎖)
- 利用連接對象和用戶請求任務請求數據庫數據
- 使用完畢之后,把連接返回給連接池 (涉及多線程競爭,加鎖)
在系統關閉前,斷開所有連接并釋放連接占用的系統資源;
連接池和線程池的關系
連接池和線程池的區別:
- 線程池:主動調用任務。當任務隊列不為空的時候從隊列取任務取執行。比如去銀行辦理業務,窗口柜員是線程,多個窗口組成了線程池,柜員從排號隊列叫號執行。
- 連接池:被動被任務使用。當某任務需要操作數據庫時,只要從連接池中取出?個連接對象,當任務使用完該連接對象后,將該連接對象放回到連接池中。如果連接池中沒有連接對象可以用,那么該任務就必須等待。比如去銀行用筆填單,筆是連接對象,我們要用筆的時候去取,用完了還回去
連接池和線程池設置數量的關系:
- ?般線程池線程數量和連接池連接對象數量?致;
- ?般線程執行任務完畢的時候歸還連接對象;
線程池設計要點
使?連接池需要預先建立數據庫連接
線程池設計思路:
- 連接到數據庫,涉及到數據庫ip、端口、用戶名、密碼、數據庫名字等;a. 連接的操作,每個連接對象都是獨立的連接通道,它們是獨立的
b. 配置最小連接數和最大連接數 - 需要?個隊列管理他的連接,比如使用list;
- 獲取連接對象
- 歸還連接對象
(同步方式)連接池的實現(偽代碼)
//數據庫連接類(一個對象對應一個Mysql/Redis連接)
class CDBConn {
int Init(); //初始化,連接數據庫操作
MYSQL* m_mysql; // 對應一個連接
};
//連接池
class CDBPool {
int Init(); // 連接數據庫,用于for循環創建CDBConn對象并且調用CDBConn->Init()
CDBConn* GetDBConn(const int timeout_ms = -1); // 獲取連接資源(即從m_free_list拿出一個連接對象)
void RelDBConn(CDBConn* pConn); // 歸還連接資源(即把連接對象放回m_free_list)
list m_free_list; // 空閑的連接
list m_used_list; // 記錄已經被請求的連接
};
//用戶請求的任務
struct job
{
void* (*callback_function)(void *arg); //線程回調函數
void *arg; //回調函數參數
struct job *next;
};
//線程池
struct threadpool{
//用戶請求的任務插入到job list中
struct job *head; //指向job的頭指針
struct job *tail; //指向job的尾指針
//工作線程
int thread_num; //線程池中開啟線程的個數
pthread_t *pthreads; //線程池中所有線程的pthread_t
};
//工作線程執行的函數
void *threadpool_function(void *arg){
while(1){
//從消息隊列中取任務
task = pop_task();
//從連接池取一個數據庫連接對象
CDBConn *pDBConn = pDBPool->GetDBConn();
//請求數據庫(同步方式,阻塞等待數據庫消息返回)
query_db(pDBConn, task);
//把數據庫連接對象放回連接池
pDBPool->RelDBConn(pDBConn);
}
}*>*>
連接池連接設置數量
連接數 = ((核心數 * 2) + 有效磁盤數)
按照這個公式,即是說你的服務器 CPU 是 4核 i7 的,那連接池連接數大小應該為 ((4*2)+1)=9
這里只是?個經驗公式。還要和線程池數量以及具體業務結合在?起
線程池
服務端epoll三種處理客戶端信息方法模型:
int n = epoll_wait();
for(n){
#if //寫法一 網絡線程處理解析以及業務邏輯后直接發給客戶端(單線程服務端)
recv(fd, buffer, length, 0);
parser();
send();
#elseif //寫法二:網絡線程把收到fd交給工作線程處理解析以及業務邏輯和發給客戶端(多線程服務器)
//該模式有缺點:可能存在多個線程同時對一個fd進行操作!
//場景:同一個客戶端短時間內發來多條請求,被分給了多個不同的線程處理,那么就出現多個線程同時對一個fd操作的情況。如果線程一個對fd寫,另一個線程對fd進行close,就會引發錯誤
//因此需要特殊處理。處理方法:加入協程。每個協程處理一個IO。但是底層依然是依賴于epoll管理所有IO
task = fd;
push_tasks(task);
#else //寫法三:網絡線程解析完信息后,交給工作線程處理業務邏輯和發給客戶端(多線程服務器)
recv(fd, buffer, length, 0);
push_task(buffer);
#endif
}
}
線程池圖示
線程池代碼演示
#include
#include
#include
#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 : %lun", 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
內存池
為什么要用內存池:
- 在需要堆內存管理一些數據的時候直接malloc,容易造成內存碎片
- 在需要堆內存管理一些數據的時候直接malloc,容易忘記free,造成內存泄漏,利于內存管理
策略
- 小塊內存(<4k):先分配一個整塊,在整塊里每次用一小塊內存
- 大塊內存(>4k):直接分配
圖示
代碼示例
#include
#include
#include
#include
#define MP_ALIGNMENT 32
#define MP_PAGE_SIZE 4096
#define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
struct mp_large_s {
struct mp_large_s *next; //下個內存結點
void *alloc; //分配的內存的頭位置
};
struct mp_node_s {
unsigned char *last; //內存結點末尾位置
unsigned char *end; //內存結點已分配內存的末尾位置
struct mp_node_s *next; //下一個內存結點
size_t failed; //嘗試利用此結點剩余空間失敗次數
};
struct mp_pool_s {
//該內存池組織了大塊內存和小塊內存 二者分配方式不一樣
size_t max; //MP_PAGE_SIZE
struct mp_large_s *large; //大塊內存
struct mp_node_s *current; //小塊內存 當前位置
struct mp_node_s head[0]; //小塊內存 頭節點位置
};
struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_nalloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
void mp_free(struct mp_pool_s *pool, void *p);
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if (ret) {
return NULL;
}
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
void mp_destory_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
h = pool->head->next;
while (h) {
n = h->next;
free(h);
h = n;
}
free(pool);
}
void mp_reset_pool(struct mp_pool_s *pool) {
struct mp_node_s *h;
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
pool->large = NULL;
for (h = pool->head; h; h = h->next) {
h->last = (unsigned char *)h + sizeof(struct mp_node_s);
}
}
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *h = pool->head;
size_t psize = (size_t)(h->end - (unsigned char *)h);
int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
if (ret) return NULL;
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s*)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m += sizeof(struct mp_node_s);
m = mp_align_ptr(m, MP_ALIGNMENT);
new_node->last = m + size;
current = pool->current;
for (p = current; p->next; p = p->next) {
if (p->failed++ > 4) {
current = p->next;
}
}
p->next = new_node;
pool->current = current ? current : new_node;
return m;
}
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) return NULL;
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n ++ > 3) break;
}
// 把 mp_large_s 結構體 放到 小塊內存的結點里
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {
void *p;
int ret = posix_memalign(&p, alignment, size);
if (ret) {
return NULL;
}
struct mp_large_s *large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = p->last;
if ((size_t)(p->end - m) >= size) {
p->last = m+size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
void *p = mp_alloc(pool, size);
if (p) {
memset(p, 0, size);
}
return p;
}
void mp_free(struct mp_pool_s *pool, void *p) {
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
free(l->alloc);
l->alloc = NULL;
return ;
}
}
}
int main(int argc, char *argv[]) {
int size = 1 << 12;
struct mp_pool_s *p = mp_create_pool(size);
int i = 0;
for (i = 0;i < 10;i ++) {
void *mp = mp_alloc(p, 512);
// mp_free(mp);
}
//printf("mp_create_pool: %ldn", p->max);
printf("mp_align(123, 32): %d, mp_align(17, 32): %dn", mp_align(24, 32), mp_align(17, 32));
//printf("mp_align_ptr(p->current, 32): %lx, p->current: %lx, mp_align(p->large, 32): %lx, p->large: %lxn", mp_align_ptr(p->current, 32), p->current, mp_align_ptr(p->large, 32), p->large);
int j = 0;
for (i = 0;i < 5;i ++) {
char *pp = mp_calloc(p, 32);
for (j = 0;j < 32;j ++) {
if (pp[j]) {
printf("calloc wrongn");
}
printf("calloc successn");
}
}
//printf("mp_reset_pooln");
for (i = 0;i < 5;i ++) {
void *l = mp_alloc(p, 8192);
mp_free(p, l);
}
mp_reset_pool(p);
//printf("mp_destory_pooln");
for (i = 0;i < 58;i ++) {
mp_alloc(p, 256);
}
mp_destory_pool(p);
return 0;
}
異步請求池
同步請求要點:
請求方作為客戶端請求后(send/sendto),立即調用(recv/recvfrom)阻塞等待結果返回。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("create socket failedn");
exit(-1);
}
printf("url:%sn", domain);
struct sockaddr_in dest;
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr(DNS_SVR);
int ret = connect(sockfd, (struct sockaddr*)&dest, sizeof(dest));
printf("connect :%dn", ret);
struct dns_header header = {0};
dns_create_header(&header);
struct dns_question question = {0};
dns_create_question(&question, domain);
char request[1024] = {0};
int req_len = dns_build_request(&header, &question, request);
int slen = sendto(sockfd, request, req_len, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr));
char buffer[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
printf("recvfrom n : %dn", n);
struct dns_item *domains = NULL;
dns_parse_response(buffer, &domains);
return 0;
}
異步請求要點:
- 請求方作為客戶端請求后(send/sendto),把fd交給epoll管理,不等待結果返回(recv/recvfrom)。
- epoll_wait在一個線程死循環中,當epoll收到消息,在進行處理(recv/recvfrom)。
struct async_context {
int epfd;
pthread_t threadid;
};
struct ep_arg {
int sockfd;
async_result_cb cb;
};
#define ASYNC_EVENTS 128
void *dns_async_callback(void *arg) {
struct async_context* ctx = (struct async_context*)arg;
while (1) {
struct epoll_event events[ASYNC_EVENTS] = {0};
int nready = epoll_wait(ctx->epfd, events, ASYNC_EVENTS, -1);
if (nready < 0) {
continue;
}
int i = 0;
for (i = 0;i < nready;i ++) {
struct ep_arg *ptr = events[i].data.ptr;
int sockfd = ptr->sockfd;
char buffer[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
//協議解析
//這里是DNS協議解析、也可以換成Mysql、Redis協議解析等。
printf("recvfrom n : %dn", n);
struct dns_item *domains = NULL;
int count = dns_parse_response(buffer, &domains);
//執行回調函數
ptr->cb(domains, count);
// close sockfd
close (sockfd);
free(ptr);
epoll_ctl(ctx->epfd, EPOLL_CTL_DEL, sockfd, NULL);
}
}
}
struct async_context* dns_async_client_init(void) {
int epfd = epoll_create(1);
if (epfd < 0) return NULL;
struct async_context* ctx = calloc(1, sizeof(struct async_context));
if (ctx == NULL) return NULL;
ctx->epfd = epfd;
int ret = pthread_create(&ctx->threadid, NULL, dns_async_callback, ctx);
if (ret) {
close(epfd);
free(ctx);
return NULL;
}
return ctx;
}
int dns_async_client_destroy(struct async_context* ctx) {
close(ctx->epfd);
pthread_cancel(ctx->threadid);
}
int dns_async_client_commit(struct async_context *ctx, async_result_cb cb) {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("create socket failedn");
exit(-1);
}
printf("url:%sn", domain);
struct sockaddr_in dest;
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr(DNS_SVR);
int ret = connect(sockfd, (struct sockaddr*)&dest, sizeof(dest));
printf("connect :%dn", ret);
struct dns_header header = {0};
dns_create_header(&header);
struct dns_question question = {0};
dns_create_question(&question, domain);
char request[1024] = {0};
int req_len = dns_build_request(&header, &question, request);
int slen = sendto(sockfd, request, req_len, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr));
struct ep_arg *ptr = calloc(1, sizeof(struct ep_arg));
if (ptr == NULL) return -1;
ptr->sockfd = sockfd;
ptr->cb = cb;
//
struct epoll_event ev;
ev.data.ptr = ptr;
ev.events = EPOLLIN; //可讀
epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev);
return 0;
}
執行順序:
- 調用dns_async_client_init創建epoll和處理epoll_wait對應的線程。
- 調用dns_async_client_commit提交請求(send/sendto),并且把對應fd交給epoll管理
- 在dns_async_callback線程中,死循環中epoll_wait檢測到EPOLLIN可讀事件,然后調用(recv/recvfrom)和callback函數處理請求返回的response事件。
-
服務器
+關注
關注
12文章
9160瀏覽量
85421 -
數據庫
+關注
關注
7文章
3799瀏覽量
64389 -
程序
+關注
關注
117文章
3787瀏覽量
81043 -
Redis
+關注
關注
0文章
375瀏覽量
10877
發布評論請先 登錄
相關推薦
評論