网络编程实战26 阻塞I/O和线程模型(线程池)

相对于进程的优势

线程上下文切换的开销比进程小的多。

上下文切换:代码被CPU执行的时候,是需要一些数据支撑的,比如程序计数器告诉CPU代码执行到哪里了,寄存器里存了当前计算的一些中间值,内存里放置了一些当前用到的变量等,从一个计算场景,切换到另一个计算场景,程序计数器、寄存器等这些值重新载入新场景的值,就是线程的上下文切换。

线程相关函数

创建线程:每个线程都有一个线程 ID(tid)唯一来标识,其数据类型为 pthread_t,一般是 unsigned int。pthread_create 函数的第一个输出参数 tid 就是代表了线程 ID,如果创建线程成功,tid 就返回正确的线程 ID。

int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
           void *(*func)(void *), void *arg);

返回:若成功则为0,若出错则为正的Exxx值

终止线程:终止一个线程最直接的方法是在父线程内调用以下函数,当调用这个函数之后,父线程会等待其他所有的子线程终止,之后父线程自己终止。

void pthread_exit(void *status)

终止子线程:可以指定某个子线程终止

int pthread_cancel(pthread_t tid)

回收线程:当调用 pthread_join 时,主线程会阻塞,直到对应 tid 的子线程自然终止。

int pthread_join(pthread_t tid, void ** thread_return)

分离线程:能在它终止后自动回收相关的线程资源,不需要调用 pthread_join 函数了

int pthread_detach(pthread_t tid)

例1:每个连接一个线程处理

缺点:线程创建和销毁开销大

pthread_create参数:描述连接最主要的是连接描述字,这里通过强制把描述字转换为 void * 指针的方式,完成了传值。虽然传的是一个指针,但是这个指针里存放的并不是一个地址,而是连接描述符的数值。
在这里插入图片描述

#include "../lib/common.h"
#define MAX_LINE 4096

void* thread_run(void *arg){
    pthread_detach(pthread_self());
    int fd = (int)arg;
    loop_echo(fd);
}

int main(int c, char **v) {
    int listener_fd = tcp_server_listen(SERV_PORT);
    pthread_t tid;
    
    while (1) {
        struct sockaddr_storage ss;
        socklen_t slen = sizeof(ss);
        int fd = accept(listener_fd, (struct sockaddr *) &ss, &slen);
        if (fd < 0) {
            error(1, errno, "accept failed");
        } else {
            pthread_create(&tid, NULL, thread_run, (void *)fd);
        }
    }

    return 0;
}

线程池处理多个连接

实现:主线程获得connfd,并将其加入blockqueue,并创建一定个数的线程,来执行thread_run函数,thread_run函数中不停从队列中取出fd。

 和前面的程序相比,线程创建和销毁的开销大大降低,但因为线程池大小固定,又因为使用了阻塞套接字,肯定会出现有连接得不到及时服务的场景。这个问题的解决还是要回到我在开篇词里提到的方案上来,多路 I/O 复用加上线程来处理,仅仅使用阻塞 I/O 模型和线程是没有办法达到极致的高并发处理能力。

blockqueue.h
没有考虑队列满的情况,可以用扩容来处理

注意:pthread_cond_wait要用while避免假唤醒

#pragma once

#include "../../lib/common.h"
typedef struct{
    int number; //数组的最大容量
    int *fd;    //fd数组
    int front;  //队列头
    int rear;   //队列尾
    pthread_mutex_t mutex;  //锁
    pthread_cond_t cond;    //条件变量
}block_queue;

//初始化队列
void block_queue_init(block_queue *blockQueue, int number){
    blockQueue->number = number;
    blockQueue->fd = calloc(number, sizeof(int));
    blockQueue->front = 0;
    blockQueue->rear = 0;
    pthread_mutex_init(&blockQueue->mutex, NULL);
    pthread_cond_init(&blockQueue->cond, NULL);
}

//往队里里放入fd
void block_queue_push(block_queue *blockQueue, int fd){
    pthread_mutex_lock(&blockQueue->mutex);
    blockQueue->fd[blockQueue->rear] = fd;
    if(++blockQueue->rear == blockQueue->number)
        blockQueue->rear = 0;
    printf("push fd %d", fd);

//从队列中取出fd
int block_queue_pop(block_queue *blockQueue){
    pthread_mutex_lock(&blockQueue->mutex);

    //如果没有新的连接字可以处理,一直等待其他线程通过条件变量发出消息
   	//使用while避免假唤醒
    while(blockQueue->front == blockQueue->rear)
        pthread_cond_wait(&blockQueue->cond, &blockQueue->mutex);
    int fd = blockQueue->fd[blockQueue->front];
    if(++blockQueue->front == blockQueue->number)
        blockQueue->front = 0;
    printf("pop fd %d", fd);
    pthread_mutex_unlock(&blockQueue->mutex);
    return fd;
}

server.c

#include "../../lib/common.h"
#include "./blockqueue.h"

#define THREAD_NUMBER 4
#define BLOCK_QUEUE_SIZE 100
#define MAX_LINE 4096
typedef struct{
    pthread_t thread_tid;   //线程id
    long thread_count;      //处理的连接
}Thread;

void thread_run(void *arg){
    int tid = pthread_self();
    pthread_detach(pthread_self());
    //arg中是结构体指针,所以用(block_queue*)转换。
    block_queue* blockQueue = (block_queue*)arg;
    while(1){
        int fd = block_queue_pop(blockQueue);
        printf("get fd in thread, fd为%d, tid为%d", fd, tid);
        loop_echo(fd);
    }
}

int main(){
    int listener_fd = tcp_server_listen(SERV_PORT);

    block_queue blockQueue;
    block_queue_init(&blockQueue, BLOCK_QUEUE_SIZE);
	
	//创建线程数组
    Thread *thread_array = calloc(THREAD_NUMBER, sizeof(Thread));
    int i;
    for(int i = 0; i < THREAD_NUMBER; i++){
    	//void*处传入的是结构体指针
        pthread_create(&(thread_array[i].thread_tid), NULL, 
                       &thread_run, (void*)&blockQueue);
    }
    while(1){
        struct sockaddr_storage ss;
        socklen_t slen = sizeof(ss);
        int fd = accept(listener_fd, (struct sockaddr*)&ss, &slen);
        if(fd < 0)
            return -1;
        else
            block_queue_push(&blockQueue, fd);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值