Linux高性能服务器编程(第二篇 深入解析高性能服务器编程)
第13章 多进程编程
1. fork系统调用
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
2. exec系列系统调用
- 替换当前进程映像,失败返回-1,否则不返回
- 原程序被新程序完全替换
- 不会关闭原程序打开的文件描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC的属性
3. 处理僵尸进程
- 对于多进程程序,父进程一般要跟踪子进程的退出状态,因此子进程结束运行时,内核不会立即释放该进程的进程表表项,以满足父进程后续对该子进程退出信息的查询。
- 子进程结束后,父进程读取其退出状态前,子进程处于僵尸态。
- 另一种原因:父进程结束或异常终止,而子进程继续运行。此时子进程的PPID被操作系统设为1,即init进程,init进程接管子进程并等待它结束。父进程退出后,子进程退出前,子进程处于僵尸态。
- 子进程结束时给父进程发送一个SIGCHLD信号,父进程可以捕获并调用waitpid函数彻底结束子进程。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* stat_loc);
pid_t wait(pid_t pid,int* stat_loc,int options);
4. 管道
5. 信号量
- 信号量原语
- 临界区:程序对共享资源访问的代码,引发进程之间的竞态条件。
- 信号量:一种特殊变量,只能取自然数值并且只支持两种操作:等待(wait)和信号(signal),通常称为P(传递,进入临界区)、V(释放,退出临界区)操作。
- 假设有信号量SV
- P(SV):如果SV值大于0,就将它减一;如果SV的值为0,则挂起进程的执行。
- V(SV):如果有其它进程因为等待SV而挂起,则唤醒之;如果没有,则将SV加一。
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id,struct sembuf* sem_ops,size_t num_sem_ops);
int semctl(int sem_id, int sem_num, int command, ...);
6. 共享内存
#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
void shmat(int shm_id,const void* shm_addr, int shmflg);
int shmdt(const void* shm_addr);
int shmctl(int shm_id,int command,struct shmid_ds* buf);
#include<sys/mman.h>
#include<sys/stat.h>
#include<fcntl.h>
int shm_open(const char* name, int oflag, mode_t mode);
int shm_unlink(const char* name);
7. 消息队列
#include<sys/msg.h>
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void* msg_ptr, size_t msg_sz, int msgflg);
int msgrcv(int msqid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgctl(int msqid, int command, struct msqid_ds* buf);
8. IPC命令
9. 在进程间传递文件描述符
- 在新进程中创建新的文件描述符,指向内核中相同的文件表项。
- 利用socket在进程间传递特殊的辅助数据。
第14章 多线程编程
1. Linux线程概述
- 线程模型
- 内核线程:运行在内核空间,由内核调度
- 用户线程:运行在用户空间,由线程库调度
- 线程的三种实现方式
- 完全在用户空间实现:内核以进程为最小单位调度,创建和调度线程无需内核干预,速度快;多个线程无法运行在多CPU上;线程优先级只在进程内有效。
- 完全由内核调度:线程创建、调度都交给内核,优缺点与完全在用户空间实现的方式相反。
- 双层调度:混合两种模式,兼具前两种方式的优点。
- Linux线程库
2. 创建线程和结束线程
#include<pthread.h>
int pthread_create(pthread_t* thread, const pthread_attr_t* attr,void* (*start_routine)(void*), void* arg);
void pthread_exit(void* retval);
int pthread_join(pthread_t thread, void** retval);
int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state,int *oldstate);
int pthread_setcanceltype(int type,int *oldtype);
3. 线程属性
4. POSIX信号量
5. 互斥锁
- 互斥锁:保护关键代码段,以确保其独占式访问,类似于二进制信号量。
6. 条件变量
- 条件变量:线程之间同步共享数据的值;提供了一种线程间的通知机制,当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
7. 线程同步机制包装类
8. 多线程环境
- 可重入函数:内部使用了静态变量的函数不可重入。
- 线程和进程:fork出的进程只保留调用fork的线程,子进程自动继承父进程的互斥锁和条件变量的状态,子进程可能不知道从父进程继承的互斥锁的具体状态,已锁住的互斥锁再次加锁会导致死锁。
- 线程和信号:进程中的所有线程共享信号,每个信号可以单独设置信号掩码,但共享信号处理函数。因此应该定义一个专门的线程处理所有信号:先在主线程创建其它子线程之前设置好信号掩码,新的子线程就会继承这个信号掩码;然后在某个 线程中调用
sigwait()
函数等待信号并处理。调用了sigwait()
函数就不应该再设置信号处理函数了。
第15章 进程池和线程池
1. 进程池和线程池概述
- 主进程选择子进程为新任务服务的方式
- 主动:随机算法;Round Robin算法(轮流选取)算法;用更智能的算法使任务在各个工作进程中更均匀地分配,减轻服务器整体压力。
- 通过共享的工作队列同步。
2. 处理多客户