网络编程:多进程和多线程编程

1. 多进程编程

1.1 fork

#include <sys/types.h>
#include <unistd.h>
// 调用失败返回 -1 设置 errno
pid_t fork( void );

子进程返回 0,父进程返回子进程 PID;

信号位图被清除(父进程的信号处理函数不再对新进程起作用)

1.2 exec 系列系统调用

替换当前进程映像,一般情况下不会返回,出错后返回 -1 并设置 errno;

注意:exec 函数不会关闭原程序打开的文件描述符,除非该 fd 设置了 SOCK_CLOEXEC 属性;

1.3 处理僵尸进程

#include <sys/types.h>
#include <sys/wait.h>
// 调用失败返回 -1 设置 errno
pid_t wait( int* stat_loc );
pid_t waitpid( pid_t pid, int* stat_loc. int options );

waitpid 只等待 pid 指定的子进程;pid 取值为 -1,和 wait 相同,等待任意一个子进程结束;
options 取值是 WNOHANG 时,waitpid 是非阻塞的:pid 指定的子进程没有结束或意外终止,立即返回 0;子进程正常退出,返回子进程 PID;

1.4 IPC 信号量

1.4.1 semget

创建一个新的信号量集,或者获取一个已经存在的信号量集;

#include <sys/sem.h>
// 成功返回一个正整数,标识信号量集
// 失败返回 -1 设置 errno
int semget( key_t key, int num_sems. int sem_flags );

key 用来标识一个全局唯一的信号量集;传递特殊键值 IPC_PRIVATE(值为 0)可以保证总是创建新的信号量集;
num_sems 指定信号量集中信号量的数目;(若是创建,必须指定;若是获取,设为 0)
sem_flags 指定该信号量的权限, 后 9 位与open中的 mode 一致;

#include <sys/sem.h>
struct ipc_perm
{
	key_t key;		/* 键值 */
	uid_t uid;		/* 所有者的有效用户 ID */
	gid_t gid;		/* 所有者的有效组 ID */
	uid_t cuid;		/* 创建者的有效用户 ID */
	gid_t cgid;		/* 创建者的有效组 ID */
	mode_t mode;	/* 访问权限 */
					/* 省略其他填充字段 */
};

struct semid_ds
{
	struct ipc_perm sem_perm;		/* 信号量的操作权限 */
	unsigned long int sem_nsems;	/* 信号量集的信号量数目 */
	time_t sem_otime;				/* 最后一次调用 semop 的时间 */
	time_t sem_ctime;				/* 最后一次调用 semctl 的时间 */
									/* 键值 */
};	

semid_ds 是与信号量集相关联的内核数据结构,semget作用是创建并初始化它,会将 sem_otime 设为 0,sem_ctime 设为当前系统时间;

1.4.2 semop

改变信号量的值,是原子操作;

unsigned short semval;			/* 信号量的值 */
unsigned short semzcnt;			/* 等待信号值变为 0 的进程数量 */
unsigned short semncnt;			/* 等待信号值增加的进程数量 */
pid_t sempid;					/* 最后一次执行 semop 操作的进程 ID */
int semop( int sem_id, struct sembuf* sem_ops, size_t num_sem_ops );

sem_id 是 semget返回的信号量集标识符;sem_ops 指向 sembuf 数组;

struct sembuf
{
	unsigned short int sem_num;
	short int sem_op;
	short int sem_flg;
}

sem_num 为信号集中信号量的编号;

在这里插入图片描述
SEM_UNDO 的含义是,当进程退出时取消正在进行的 semop 操作,会更新进程的 semadj 变量,,来跟踪进程对信号量的修改情况;

1.4.3 semctl

对信号量直接控制

int semctl( int sem_id, int sem_num, int command, ... );

command 为要执行的命令,有些命令需要第 4 个参数,推荐如下形式

union semun
{
	int val;					/*用于 SETVAL 命令*/
	struct semid_ds* buf;		/*用于 IPC_STAT 和 IPC_SET 命令*/
	unsigned short* array;		/*用于 GETALL 和 SETALL 命令*/
	struct seminfo* __buf;		/*用于 IPC_INFO 命令*/
};

常用命令为

命令含义成功时返回值
IPC_RMID移除信号量集,唤醒所有等待该信号量集的进程(semop返回错误,errno 为 EIDRM)0
SETVAL将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新0

1.5 共享内存

最高效的 IPC 机制;

1.5.1 shmget

创建一段新的共享内存或获取一段已经存在的共享内存;

#include <sys/shm.h>
// 成功返回正整数,为共享内存的标识;失败返回 -1 设置 errno
int shmget( key_t key, size_t size, int shmflg );

这三个参数与 semget含义相同,size 指定共享内存的大小,单位是字节;
shmflg 支持额外的两个标志:

  • SHM_HUGETLB;系统使用“大页面”分配共享内存,类似于 mmap的 MAP_HUGETLB 标志;
  • SHM_NORESERVE;不为共享内存保留交换分区,类似于 mmap的 MAP_NORESERVE 标志;这样当物理内存不足时,对该共享内存执行写操作将触发 SIGSEGV 信号;

shmget创建共享内存,所有字节都被初始化为 0,与之关联的内核数据结构为 shmid_ds 被创建和初始化;

struct shmid_ds
{
	struct ipc_perm shm_perm;	/* 共享内存的操作权限 */
	size_t shm_segsz;			/* 共享内存的大小,单位是字节 */
	__time_t shm_atime;			/* 对这段内存最后一次调用 shmat 的时间 */
	__time_t shm_dtime;			/* 对这段内存最后一次调用 shmdt 的时间 */
	__time_t shm_ctime;			/* 对这段内存最后一次调用 shmctl 的时间 */
	__pid_t shm_cpid;			/* 创建者的 PID */
	__pid_t shm_lpid;			/* 最后一次执行 shmat 或 shmdt 操作的进程 PID */
	shmatt_t shm_nattach;		/* 关联到此共享内存的进程数量 */
	/* 省略填充字段 */
};

初始化时,shm_lpid、shm_nattach、shm_atime、shm_dtime 设置为 0,shm_ctime 设置为当前时间;

1.5.2 shmat 和 shmdt

关联/分离到进程的地址空间

void* shmat( int shm_id, const void* shm_addr, int shmflg );
int shmdt( const void* shm_addr );

shm_addr 指定将共享内存关联到进程的哪块空间地址;

  • shm_addr 为 NULL,由操作系统选择,推荐的做法;
  • shm_addr 非空,并且 SHM_RND 未设置,共享内存关联到 addr 指定地址处;
  • shm_addr 非空,并且设置了 SHM_RND ,被关联的地址是 [shm_addr - (shm_addr%SHMLBA)];

shmflg 还支持如下标志:

  • SHM_RDONLY;进程仅能读取共享内存中的内容;
  • SHM_REMAP;如果地址 shm_addr 已经被关联到一段共享内存上,重新关联;
  • SHM_EXEC;指定对共享内存段的执行权限;

shmat 成功返回被关联的地址,失败返回(void *) -1 并设置 errno;将 shm_nattach++,shm_lpid 设置为调用进程 PID,shm_atime 设置为当前时间;

shmdt 成功时返回 0,失败返回 -1 设置 errno;将 shm_nattach–,shm_lpid 设置为调用进程 PID,shm_dtime 设置为当前时间;

1.5.3 shmctl
// 失败时返回 -1 设置 errno
int shmctl( int shm_id, int command, struct shmid_ds* buf );
命令含义成功时返回值
SETVAL将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新0
1.5.4 共享内存的 POSIX 方法

通过打开同一个文件, mmap 可以实现进程间的内存共享;
Linux 提高了另一种利用 mmap 在无关进程间共享内存的方式,无需任何文件的支持;

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
// 创建或打开一个 POSIX 共享内存对象
int shm_open( const char* name, int oflag, mode_t mode );
int shm_unlink( const char* name );

shm_open 成功返回一个文件描述符,该 fd 可用于后续的 mmap 调用,失败返回 -1 设置 errno。
shm_unlink 将 name 指定共享内存对象标记为等待删除;当所有使用该共享对象的进程都使用 ummap将它从进程中分离后,系统将销毁该对象;

ftruncate(fd, length)会将参数 fd 指定的文件大小改为参数 length 指定的大小。

1.6 消息队列

两个进程间传递二进制块数据一种简单有效的方式;
每个数据块都有一个特定的类型,接收方可以根据类型来选择地接收数据;

1.6.1 msgget

创建或获取消息队列

#include <sys/msg.h>
int msgget( key_t key, int msgflg );

参数含义与 semget一致,与之关联的内核数据结构 msqid_ds 被创建并初始化;

struct msqid_ds
{
	struct ipc_perm msg_perm;		/* 操作权限 */
	time_t msg_stime;				/* 最后一次调用 msgsnd 的时间 */
	time_t msg_stime;				/* 最后一次调用 msgrcv 的时间 */
	time_t msg_stime;				/* 最后一次被修改的时间 */
	unsigned long __msg_cbytes;		/* 消息队列中已有的字节数 */
	msgqnum_t msg_qnum;				/* 消息队列中已有的消息数 */
	msglen_t msg_qbytes;			/* 消息队列允许的最大字节数 */
	pid_t msg_lspid;				/* 最后执行 msgsnd 的进程 PID */
	pid_t msg_lrpid;				/* 最后执行 msgrcv 的进程 PID */
};
1.6.2 msgsnd

把一条消息添加到消息队列中;

// 成功返回 0,失败返回 -1
int msgsnd( int msqid, const void* msg_ptr, size_t msg_sz, int msgflg );

msg_ptr 指向一个准备发送的消息

struct msgbuf
{
	long mtype;			/* 消息类型,必须为正整数 */
	char mtext[512];	/* 消息数据 */
};

msg_sz 是消息数据部分的长度,可以为 0 表示没有消息数据;
msgflg 设置 IPC_NOWAIT 标志时,表示非阻塞;如果消息队列满,默认情况下会阻塞,如果设置了 IPC_NOWAIT 将立即返回并设置 errno 为 EAGAIN;

处于阻塞状态的 msgsnd 可能被中断:

  • 消息队列被移除,会立即返回,errno 为 EIDRM;
  • 程序接收到信号,立即返回,errno 为 EINTR;
1.6.3 msgrcv

从消息队列中获取消息

// 成功返回 0,失败返回 -1
int msgrcv( int msqid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg );

(1)msg_ptr 用于存储接收到的消息;
(2)msgtype 指定接收何种类型的消息;

  • 为 0,表示读取消息队列中的第一个消息;
  • 大于 0,读取第一个类型为 msgtype 的消息;
  • 小于0,读取第一个类型值比 msgtype 绝对值小的消息;

(3)msgflg

  • IPC_NOWAIT;如果消息队列为空,立即返回,errno 设为 ENOMSG;
  • MSG_EXCEPT;msgtype 大于 0,接收第一个非 msgtype 的消息;
  • MSG_NOERROR;如果消息数据部分长度超过了 msg_sz,将其截断;

(4)阻塞的 msgrcv 被中断情况与 msgsnd 一致;

1.6.4 msgctl
// 失败返回 -1
int msgctl( int msqid, int command, struct msqid_ds* buf );

1.7 IPC 命令

上述 3 种进程间通信方式都使用了全局唯一的键值来描述一个共享资源;
Linux 下 ipcs 命令可以查看当前系统上的共享资源实例;

1.8 进程间传递文件描述符

可以利用 UNIX 域 socket 在进程间传递特殊辅助数据

利用 sendmsgrecvmsg 可以发送附属数据

2. 多线程编程

内核线程:运行在内核空间,由内核调度;
用户线程:运行在用户空间,由线程库调度;

当进程的一个内核线程获得 CPU 使用权时,加载并允许一个用户线程,内核线程相当于用户线程运行的“容器”;

一个进程可以拥有 M 个内核线程和 N 个用户线程,M <= N,它们之间的比值是固定的;

(1)完全在用户空间实现;

M:N = 1:N

线程库利用 longjmp 来切换线程;一个进程的所有执行线程共享该进程的时间片,对外表现出相同的优先级;

优点:创建和调度线程的速度非常快,不需要内核参与,不占用额外的内核资源;

缺点:一个进程的多个线程无法运行在不同的 CPU 上;

(2)完全由内核调度;

M:N = 1:1,与上述优缺点互换

(3)双层调度;结合了前两种方式的优点;

2.1 线程库

Linux 下目前基本使用的都是 NPTL,采用 1:1 方式实现的;

2.2 创建和结束线程

(1)

#include <pthread.h>
// 成功返回 0,失败返回错误码
int pthread_create( pthread_t* thread, const pthread_attr_t* attr,
					void* (*start_routine)( void* ), void* arg );

thread 的类型 pthread_t 是无符号长整形,为新线程的标识符;
attr 设置新线程的属性,设为 NULL 代表使用默认线程属性;

一个用户可打开的线程数量不能超过 RLIMIT_NPROC 限制;

(2)在结束时最好调用如下函数;

void pthread_exit( void* retval );

retval 向线程的回收者传递其退出信息;

(3)等待其他线程结束(回收)

int pthread_join( pthread_t thread, void** retval );

会一直阻塞直到被回收的线程结束;
成功返回 0,失败返回错误码;

错误码描述
EDEADLK可能引起死锁;比如两个线程互相针对对方调用 pthread_join 或 线程对自身调用 pthread_join
EINVAL目标线程不可回收,或已经有其他线程在回收该目标线程
ESRCH目标线程不存在

(4)异常终止一个线程;

int pthread_cancel( pthread_t thread );

接收到这个请求的目标线程可以决定是否允许被取消以及如何取消;

int pthread_setcancelstate( int state, int* oldstate );
int pthread_setcanceltype( int type, int* oldtype );

第一个参数分别用于设置线程的取消状态(是否允许取消)和取消类型(如何取消)
第二个参数记录了原来的状态和类型;

state 可选值为:

  • PTHREAD_CANCEL_ENABLE,允许线程被取消,线程创建时的默认状态
  • PTHREAD_CANCEL_DISABLE,禁止线程被取消;如果收到取消请求,会将请求挂起,直到该线程允许被取消

type 可选值为:

  • PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消,接收到取消请求立即采取行动;
  • PTHREAD_CANCEL_DEFERRED,允许目标线程推迟行动,直到调用了下面几个取消点函数中的一个:pthread_join、pthread_testcancel、pthread_cond_wait、pthread_cond_timedwait、sem_wait 和 sigwait;

2.3 线程属性

各种线程属性全部包含在一个字符数组中;

#include <bits/pthreadtypes.h>
#define __SIZEOF_PTHREAD_ATTR_T 36
typedef union
{
	char __size[__SIZEOF_PTHREAD_ATTR_T];
	long int __align;
} pthread_attr_t;
// 初始化线程属性对象
int pthread_attr_init( pthread_attr_t* attr );
// 销毁线程属性对象,被销毁的对象只有再次初始化之后才能继续使用
int pthread_attr_destroy( pthread_attr_t* attr );
// 获取或设置线程属性对象的某个属性
int pthread_attr_getdetachstate( const pthread_attr_t* attr, int* detachstate );
int pthread_attr_setdetachstate( pthread_attr_t* attr, int detachstate );
int pthread_attr_getstackaddr( const pthread_attr_t* attr, void** stackaddr );
int pthread_attr_setstackaddr( pthread_attr_t* attr, void* stackaddr );
int pthread_attr_getstacksize( const pthread_attr_t* attr, size_t* stacksize );
int pthread_attr_setstacksize( pthread_attr_t* attr, size_t stacksize );
int pthread_attr_getstack( const pthread_attr_t* attr, void** stackaddr, size_t* stacksize );
int pthread_attr_setstack( pthread_attr_t* attr, void* stackaddr, size_t stacksize );
int pthread_attr_getguardsize( const pthread_attr_t* attr, size_t* guardsize);
int pthread_attr_setguardsize( pthread_attr_t* attr, size_t guardsize);
int pthread_attr_getschedparam( const pthread_attr_t* attr, struct sched_param* param );
int pthread_attr_setschedparam( pthread_attr_t* attr, const struct sched_param* param );
int pthread_attr_getschedpolicy( const pthread_attr_t* attr, int* policy );
int pthread_attr_setschedpolicy( pthread_attr_t* attr, int policy );
int pthread_attr_getinheritsched( const pthread_attr_t* attr, int* inherit);
int pthread_attr_setinheritsched( pthread_attr_t* attr, int inherit );
int pthread_attr_getscope( const pthread_attr_t* attr, int* scope );
int pthread_attr_setscope( pthread_attr_t* attr, int scope );
  • detachstate,线程的脱离状态;
    • PTHREAD_CREATE_JOINABLE;可被回收;(默认值)
    • PTHREAD_CREATE_DETACH;脱离线程,退出时自行释放资源;
  • stackaddr 和 stacksize,线程堆栈的起始地址和大小;(一般由系统自动管理)
  • guardsize,保护区域大小;如果大于 0,系统创建线程时会在其堆栈的尾部额外分配 guardsize 字节空间,作为保护堆栈不被错误地覆盖的区域;如果通过 pthread_attr_setstackaddrpthread_attr_setstack手动设置线程的堆栈,会忽略该属性;
  • schedparam,线程调度参数;该结构体目前只有一个整性类型成员 sched_priority,表示线程运行优先级;
  • schedpolicy,线程调度策略;
    • SCHED_FIFO;先进先出调度
    • SCHED_RR;轮转算法调度,这两种调度算法都具备实时调度功能,只能用于以超级用户身份运行的进程;
    • SCHED_OTHER; (默认值)
  • inheritsched,是否继承调用线程的调度属性;PTHREAD_INHERIT_SCHED 和 PTHREAD_EXPLICIT_SCHED,前者表示沿用其创建者的线程调度参数,这种情况下再设置新线程的调度参数将没有任何效果;后者表明需要为新线程设定新的调度参数;
  • scope,线程间竞争 CPU 的范围,即线程优先级的有效范围;目前 Linux 只支持 PTHREAD_SCOPE_SYSTEM 表明与系统中所有线程一起竞争 CPU 的使用;

2.4 POSIX 信号量

接下来是专门用于线程同步的机制;

#include <semaphore.h>
// 成功时返回 0,失败返回 -1 设置 errno
int sem_init( sem_t* sem, int pshared, unsigned int value );
int sem_destroy( sem_t* sem );
int sem_wait( sem_t* sem );
int sem_trywait( sem_t* sem );
int sem_post( sem_t* sem );

(1)sem_init 初始化一个未命名信号量;pshared 指定信号量的类型,其值为 0,表示这个信号量是当前进程局部信号量,否则可以在多个进程间共享;value 指定信号量的初始值;

注意:初始化一个已经被初始化的信号量将导致不可预期的后果;

(2)sem_destroy 销毁信号量,释放其占用的内核资源;如果销毁一个正被其他线程等待的信号量,将导致不可预期的结果;

(3)sem_wait 以原子操作方式将信号量的值减 1,如果信号量值为 0,将被阻塞;

(4)sem_trywaitsem_wait 类似,不过其始终立即返回;当信号量值为 0 时,其返回 -1 设置 errno 为 EAGAIN;

(5)sem_post 以原子操作方式将信号量加 1;

2.5 互斥锁

2.5.1 基础 API
#include <pthread.h>
// 成功时返回 0,失败返回错误码
int pthread_mutex_init( pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr );
int pthread_mutex_destroy( pthread_mutex_t* mutex );
int pthread_mutex_lock( pthread_mutex_t* mutex );
int pthread_mutex_trylock( pthread_mutex_t* mutex );
int pthread_mutex_unlock( pthread_mutex_t* mutex );

(1)初始化互斥锁,mutexattr 指定互斥锁的属性,设为 NULL,表示使用默认属性;

可以使用如下方式初始化一个互斥锁,实际上是将互斥锁各字段都初始化为 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

(2)pthread_mutex_trylock 对已经加锁的互斥锁操作时,返回错误码 EBUSY;

2.5.2 互斥锁属性
// 成功时返回 0,失败返回错误码
int pthread_mutexattr_init( pthread_mutexattr_t* attr );
int pthread_mutexattr_destroy( pthread_mutexattr_t* attr );
int pthread_mutexattr_getpshared( const pthread_mutexattr_t* attr, int* pshared );
int pthread_mutexattr_setpshared( pthread_mutexattr_t* attr, int pshared );
int pthread_mutexattr_gettype( const pthread_mutexattr_t* attr, int* type );
int pthread_mutexattr_settype( pthread_mutexattr_t* attr, int type );
  • pshared 的可选值为:
    • PTHREAD_PROCESS_SHARED,互斥锁跨进程共享;
    • PTHREAD_PROCESS_PRIVATE,只能被锁初始化线程隶属同一个进程的线程共享;
  • type 指定互斥锁的类型
    • PTHREAD_MUTEX_NORMAL,普通锁(默认类型);请求该锁的线程形成一个等待队列,在该锁解锁后按优先级获得它;同一个线程重复加锁会发生死锁;
    • PTHREAD_MUTEX_ERRORCHECK,检错锁;一个线程对一个已经加锁的检错锁再次加锁时,返回 EDEADLK;对一个已经被其他线程加锁的检错锁解锁,或对一个已经解锁的检错锁再次解锁,返回 EPERM;
    • PTHREAD_MUTEX_RECURSIVE,嵌套锁;允许一个线程在释放锁之前多次对它加锁而不发生死锁,释放锁时必须执行相应次数的解锁操作;对一个已经被其他线程加锁的嵌套锁解锁,或对一个已经解锁的嵌套锁再次解锁,返回 EPERM;
    • PTHREAD_MUTEX_DEFAULT,默认锁;

2.6 条件变量

条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的线程;

#include <pthread.h>
// 成功时返回 0,失败返回错误码
int pthread_cond_init( pthread_cond_t* cond, const pthread_mutexattr_t* cond_attr );
int pthread_cond_destroy( pthread_cond_t* cond);
int pthread_cond_broadcast( pthread_cond_t* cond);
int pthread_cond_signal( pthread_cond_t* cond);
int pthread_cond_wait( pthread_cond_t* cond, pthread_mutex_t* mutex );

(1)初始化也可以采用如下方式

pthread_mutex_t mutex = PTHREAD_COND_INITIALIZER;

(2)销毁一个正在被等待的条件变量将失败并返回 EBUSY;

(3)pthread_cond_broadcast 以广播方式唤醒所有等待目标条件变量的线程;

pthread_cond_signal 唤醒一个等待目标条件的线程;具体哪个线程被唤醒,取决于线程的优先级和调度策略;

若想唤醒指定线程,需要使用 pthread_cond_broadcast ,在线程内判断是否是目标线程,若是开始执行,否则继续等待;

(4)pthread_cond_wait 等待目标条件变量,在调用pthread_cond_wait 前,必须确保 mutex 已经加锁;其在执行时,首先把调用线程放入条件变量的等待队列中,然后将 mutex 解锁;

2.7 多线程环境

可重入函数(线程安全的):一个函数可以被多个线程同时调用且不发生竞态条件;

2.7.1 进程和线程

多线程程序下的某个线程调用 fork函数,新创建的子进程只有一个执行线程,是调用 fork 函数的那个线程的完整复制;并且子进程会自动继承 fork 时父进程中互斥锁(条件变量)的状态;

这可能会导致问题,因为子进程可能并不清除父进程中互斥锁的具体状态;

// 成功返回 0,失败返回错误码
int pthread_atfork( void (*prepare)(void), void (*parent)(void), void (*child)(void) );

该函数将建立 3 个 fork 句柄帮助我们清理互斥锁的状态,prepare 句柄将在 fork 调用创建出子进程前执行,可以用来锁住所有父进程中的互斥锁;parent 句柄则是在 fork 创建出子进程之后, fork 返回前,在父进程中被执行,释放所有在 prepare 句柄中被锁住的互斥锁;child 是 fork 返回之前,在子进程中被执行,释放所有在 prepare 句柄中被锁住的互斥锁;

在 prepare 中加锁时,会等待其他线程释放锁,因此可能会阻塞;

2.7.2 线程和信号

每个线程都可以独立的设置信号掩码

#include <pthread.h>
#include <signal.h>
// 成功返回 0,失败返回错误码
int pthread_sigmask( int how, const sigset_t* newmask, sigset_t* oldmask );

进程中的所有线程共享该进程的信号,线程库将根据线程掩码决定把信号发给哪个线程;所有线程共享信号处理函数;应该定义一个专门线程来处理所有的信号

#include <signal.h>
// 成功返回 0,失败返回错误码
int sigwait( const sigset_t* set, int* sig );

将一个信号发送给指定的线程:

#include <signal.h>
// 成功返回 0,失败返回错误码
int pthread_kill( pthread_t thread, int sig );

如果 sig 为 0,不发送信号,可以利用这种方式检测目标线程是否存在;

3 进程池和线程池

(1)如何选择子进程:

  • 主进程使用某种算法来主动选择子进程;如随机算法或 Round Robin 算法;
  • 主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上;当有新的任务到来时,主进程将任务添加到工作队列中;

(2)通知子进程,并传递必要的数据;使用预先建好的一条管道;

多线程中可以把数据定义为全局的,就会被所有线程共享;

3.1 半同步/半异步进程池

(1)主函数创建监听 socket;

(2)主函数使用 监听 socket 初始化进程池类(进程池类使用单例模式,由静态成员函数和静态变量实现,在函数内部 new 该类,返回这个指针,之后由用户使用完毕后,用户负责释放这个指针)

进程池类构造时,创建多个子进程;(这些子进程最终都会执行主函数剩余部分,因此把关闭 连接socket 和 delete 进程池类 都放在主函数后面执行,对外表现是:在哪里初始化就在哪里销毁,逻辑清晰)

(3)运行

每个进程都创建自己的 epollfd

父进程只负责监听 socket 和 各种信号,如果有请求连接,就将通过管道发送给某个子进程;

子进程负责 与父进程通信管道 、连接 socket 和 各种信号,子进程还负责读取连接 socket 上的数据,具体业务逻辑会 fork 子进程负责;

3.2 半同步/半反应堆线程池

请求队列由 list 实现,并使用互斥锁进行保护;

在 C++ 中使用 pthread_create 时,该函数的第 3 个参数必须指向一个静态函数;在静态函数中使用类的成员变量,需要把类的对象作为参数传递给该静态函数;

为每个客户都维护了一个 http_conn 类

主线程负责监听 socket 和 连接 socket,对连接 socket 的读写也都与主线程完成,具体业务逻辑由子线程完成;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值