相对于进程的优势
线程上下文切换的开销比进程小的多。
上下文切换:代码被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;
}