多线程编程
14.1 Linux线程概述
14.1.1 线程模型
线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。线程可分为内核线程和用户线程。一个进程可拥有M个内核线程N个用户线程,且M<=N。按照M:N的取值,线程的实现方式可分为:完全在用户空间实现、完全由内核调度和双层调度。
完全在用户空间实现的线程的优点:创建和调度线程都无需内核的干预,速度加偶爱,且不占用额外的内核资源,既是一个进程有多个线程,也不会对系统造成明显的影响。缺点:对于多处理器,一个进程的多个线程无法运行在不同的CPU上。
完全由内核调度的模式优缺点和完全在用户空间实现的优缺点正好互换。双层调度则结合了前面两种优点:不但不会消耗过多的内核资源,而且线程切换速度较快,可充分利用多处理器的优势。
14.1.2 Linux线程库
14.2 创建线程和结束线程
#include <pthread.h>
// 创建一个线程
int pthread_creat(pthread_t* thread, const pthread_addr_t* attr, void* (*start_routine)(void*), void* arg);
// pthread_t定义
#include <bits/pthreadtypes.h>
typedef unsigned long int pthread_t;
// 线程函数在结束时调用,确保安全、干净的退出
void pthread_exit(void* retal);
// 调用函数来回收线程,即等待其他线程结束
void pthread_join(pthread_t thread, void** retval);
// 异常终止一个线程,即取消线程
int pthread_cancel(pthread_t thread);
// 接收到取消请求的目标线程可以决定是否允许被取消已经如何取消
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcancelstate(int type, int *oldtype);
14.3 线程属性
#include <pthread.h>
#include <bits/pthreadtypes.h>
#define __SIZEOF_PTHREAD_ATTR_T 36
// pthread_attr_t 结构体定义了一套完整的线程属性
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);
// 设置和获取线程属性函数
// 略
线程属性含义:
- detachstate:线程的脱离状态。PTHREAD_CREATE_JOINABLE和PTHREAD_CREAT_DETACH两个可选值,前者指定线程可被回收,后者使调用线程脱离与进程中其他线程的同步;
- stackaddr和stacksize:线程堆栈的起始地址和大小;
- guardsize:保护区域大小;
- schedparam:线程调度参数;
- schedpolicy:线程调度策略。SCHED_FIFO、SCHED_RR和SCHED_OTHER,其中第三个是默认值,RR是轮转算法、FIFO是先进先出方法;
- inheritsched:是否继承调用线程的调度属性;
- scope:线程间竞争CPU的范围,即线程优先级的有效范围。
14.4 POSIX信号量
常用的POSIX信号量:
#include <semaphore.h>
// 初始化一个未命名的信号量
int sem_init(sem_t* sem, int pshared, unsigned int value);
// 小蕙心好凉,以释放其占用的内核资源
int sem_destory(sem_t* sem);
// 以原子操作的方式将信号量减一,若信号量为0,则sem_wait将被阻塞,知道这个信号量有非0值
int sem_wait(sem_t* sem);
// 与sem_wait类似,不过它始终立即返回,无论被操作的信号量是否有非0值
int sem_trywait(sem_t* sem);
// 以原子操作的方式将信号量值加一
int sem_post(sem_t* sem);
14.5 互斥锁
14.5.1 互斥锁基础API
#include <pthread.h>
// 初始化互斥锁,也可以用:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
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(phtread_mutex_t* mutex);
// 与lock类似,其始终立即返回
int pthread_mutex_trylock(pthread_mutex_t* mutex);
// 给互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
14.5.2 互斥锁属性
#include <pthread.h>
// 初始化互斥锁属性对象
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
// 销毁互斥锁属性对象
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
// 获取和设置互斥锁的pshared属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr, int* pshared);
int phtread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared);
// 获取互斥锁的type属性
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
四种类型的互斥锁:
- PTHREAD_MUTEX_NORMAL:普通锁
- PTHREAD_MUTEX_ERRORCHECK:检错锁
- PTHREAD_MUTEX_RECURSIVE:嵌套锁
- PTHREAD_MUTEX_DEFAULT:默认锁
14.5.3 死锁举例
略
14.6 条件变量
// 条件变量:用于线程之间共享同步数据的值
// 提供了一个线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程
#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cont_t* cond, const pthread_condattr_t* cond_attr);
// 销毁条件变量
int pthread_cond_destory(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);
14.7 线程同步机制包装类
#ifndef LOCKER_H
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>
// 封装信号量的类
class sem{
public:
// 创建并初始化常量
sem(){
if(sem_init(&m_sem, 0, 0) != 0){
// 构造函数没有返回值,可通过抛出异常来报告错误
throw std::exception();
}
}
// 销毁信号量
~sem(){
sem_destroy(&m_sem);
}
// 等待信号量
bool wait(){
return sem_wait(&m_sem) == 0;
}
// 增加信号量
bool post(){
return sem_post(&m_sem) == 0;
}
private:
sem_t m_sem;
};
// 封装互斥锁的类
class locker{
public:
// 创建并初始化互斥锁
locker(){
if(pthread_mutex_init(&m_mutex, NULL)!=0){
throw std::exception();
}
}
// 销毁互斥锁
~locker(){
pthread_mutex_destroy(&m_mutex);
}
// 获取互斥锁
bool lock(){
return pthread_mutex_lock(&m_mutex)==0;
}
// 释放互斥锁
bool unlock(){
return pthread_mutex_unlock(&m_mutex)==0;
}
private:
pthread_mutex_t m_mutex;
};
// 封装条件变量的类
class cond{
public:
// 创建并初始化结构变量
cond(){
if(pthread_mutex_init(&m_mutex, NLL)!=0){
throw std::exception();
}
if(pthread_cond_init(&m_cond, NULL)!=0){
pthread_mutex_destroy(&m_mutex);
throw std::exception();
}
}
// 销毁条件变量
~cond(){
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
}
// 等待条件变量
bool wait(){
int ret = 0;
pthread_mutex_lock(&m_mutex);
ret = pthread_cond_wait(&m_cond, &m_mutex);
pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
// 唤醒等待条件变量的栈
bool signal(){
return pthread_cond_signal(&m_cond) == 0;
}
private:
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
};
#endif
14.8 多线程环境
14.8.1 可重入函数
入宫一个函数可以被多个线程同时调用且不发生静态条件,则称其为线程安全的,或者称其为可重入函数。
14.8.2 线程和进程
#include <pthread.h>
// 确保fork调用后父进程和紫禁城都有一个清楚的锁状态
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
14.8.3 线程和信号
#include <pthread.h>
#include <signal.h>
// 设置信号掩码
int pthread_sigmask(int how, const sigset_t* newmask, sigset_t* oldmask);
// 明确地将一个信号发送给指定的线程
int pthread_kill(pthread_t thread, int sig);