一.基本概念
进程中有哪些资源:代码段指令,只读段,全局段,静态数据,段,堆,栈,命令行参数,环境变量表,代码的执行者(线程)。
线程:在进程中,负责执行代码的一个单位,它是进程的一部分,一个进程至少要有一个线程(主线程),进程也可以有多个线程(创建)
线程中的代码段指令,只读段,全局段,静态数据,段,堆,命令行参数,环境变量表,文件描述符,信号处理函数,等资源共享
线程之间,栈空间是私有的
线程是进程的一个实体,是操作系统独立调度和分派任务的基本单位。
二.POSIX线程
Unix和Linux是天生骄傲(不支持线程),通过添加额外的线程库可以使用,在编译多线程代码时需要添加 -lpthread,头文件pthread.h
对线程的操作:
创建线程
销毁线程
分离线程
联合线程
查询线程属性
设置线程属性
对于线程来说,最重要的是解决脏数据问题(线程同步),对于进程来说,解决通信问题(IPC)
三.创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建线程
thread:返回值,获取线程id
attr:参数,设置线程属性
start_routine:参数,线程的入口函数
arg:给线程入口函数的参数
1、同一个进程的多个线程都在同一个地址空间内活动,因此相对于进程,线程的系统开销小,任务切换快,它们可以执行相同的代码,也可以执行不同的代码。
2、线程间的数据交换不需要依赖于类似IPC的特殊通信机制,简单而高效,每个线程拥有自己独立的线程ID、寄存器信息、函数栈、错误码和信号掩码,线程之间存在优先级的差异。
3、main函数即主线程,main函数返回即主线程结束,主线程结束即进程结束,进程一但结束其所有的线程即结束。
4、应设法保证在线程过程函数执行期间,其参数所指向的目标持久有效。
四、对线程的操作
1、等待线程
int pthread_join (pthread_t thread, void** retval);
功能:等待thread参数所标识的线程结束
retval:返回值,线程入口函数的返回值
返回值:成功返回0,失败返回错误码。
线程过程函数将所需返回的内容放在一块内存中,返回该内存的地址,要保证这块内存在函数返回后,即线程结束,以后依然有效;
若retval参数非NULL,则pthread_join函数将线程过程函数所返回的指针,拷贝到该参数所指向的内存中;
若线程过程函数所返回的指针指向动态分配的内存,则还需保证在用过该内存之后释放之。
2、获取线程id
pthread_t pthread_self (void);
功能:返回当前线程的id,此函数不会执行失败。
3、比较两个线程
int pthread_equal (pthread_t t1, pthread_t t2);
功能:比较两个id是否是同一个线程
返回值:两个id相等,则返回非零,否则返回0。
某些实现的pthread_t不是unsigned long int类型,可能是结构体类型,无法通过“==”判断其相等性。
4、终止线程
1) 从线程过程函数中return。
2) 调用pthread_exit函数。
void pthread_exit (void* retval);
retval和线程过程函数的返回值语义相同。
5、线程的执行轨迹
1) 同步方式(非分离状态):
创建线程之后调用pthread_join函数等待其终止,并释放线程资源。
2) 异步方式(分离状态):
无需创建者等待,线程终止后自行释放资源。
int pthread_detach (pthread_t thread);
功能:使线程进入分离(DETACHED)状态。
返回值:成功返回0,失败返回错误码。
处于分离状态的线程终止后自动释放线程资源,且不能被pthread_join函数等待。
6、取消线程
1) 向指定线程发送取消请求
int pthread_cancel (pthread_t thread);
成功返回0,失败返回错误码。
注意:只是向线程发出取消请求,并不等待线程终止。
缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。
在取消点处,线程检查其自身是否已被取消了,并做出相应动作。
当线程调用一些特定函数时,取消点会出现。
2) 设置调用线程的可取消状态
int pthread_setcancelstate (int state,int* oldstate);
成功返回0,并通过oldstate参数输出原可取消状态(若非NULL),失败返回错误码。
state取值:
PTHREAD_CANCEL_ENABLE:接受取消请求
PTHREAD_CANCEL_DISABLE:忽略取消请求。
3) 设置调用线程的可取消类型
int pthread_setcanceltype (int type, int* oldtype);
成功返回0,并通过oldtype参数输出原可取消类型(若非NULL),失败返回错误码。
type取值:
PTHREAD_CANCEL_DEFERRED延迟取消(缺省)。
被取消线程在接收到取消请求之后并不立即响应,
而是一直等到执行了特定的函数(取消点)之后再响应该请求。
PTHREAD_CANCEL_ASYNCHRONOUS - 异步取消。
被取消线程可以在任意时间取消,不是非得遇到取消点才能被取消。
但是操作系统并不能保证这一点。
练习:悟空聊天室
服务端:
1.使用tcp协议进行网络通信
2.为每一个客户端创建一个线程进行服务
3.收到某个客户端的消息,转发给其他客户端(除了它自己)
4.服务器最大在线人数50个
客户端:
1.输入昵称
2.一个线程发送消息
3.另一个线程接收消息
五、竞争与同步
当多个线程同时访问其所共享的进程资源时,需要相互协调,以防止出现数据不一致、不完整的问题。这就叫线程同步。
六、互斥量、
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
功能:初始化互斥量
//亦可 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock (pthread_mutex_t* mutex);
功能:加锁
int pthread_mutex_unlock (pthread_mutex_t* mutex);
功能:解锁
int pthread_mutex_destroy (pthread_mutex_t* mutex);
功能:销毁互斥量
1) 互斥量被初始化为非锁定状态;
2) 线程1调用pthread_mutex_lock函数,立即返回,互斥量呈锁定状态;
3) 线程2调用pthread_mutex_lock函数,阻塞等待;
4) 线程1调用pthread_mutex_unlock函数,互斥量呈非锁定状态;
5) 线程2被唤醒,从pthread_mutex_lock函数中返回,互斥量呈锁定状态;
七、信号量
信号量是一个计数器,用于控制访问有限共享资源的线程数。
注意:线程使用的信号量不在pthread.h中,而是semaphore.h
// 创建信号量
int sem_init (sem_t* sem, int pshared,unsigned int value);
sem - 信号量ID,输出。
pshared - 一般取0,表示调用进程的信号量。
非0表示该信号量可以共享内存的方式,
为多个进程所共享(Linux暂不支持)。
value - 信号量初值。
// 信号量减1,不够减即阻塞
int sem_wait (sem_t* sem);
// 信号量减1,不够减即返回-1,errno为EAGAIN
int sem_trywait (sem_t* sem);
// 信号量减1,不够减即阻塞,
// 直到abs_timeout超时返回-1,errno为ETIMEDOUT
int sem_timedwait (sem_t* sem,const struct timespec* abs_timeout);
struct timespec {
time_t tv_sec; // Seconds
long tv_nsec; // Nanoseconds [0 - 999999999]
};
// 信号量加1
int sem_post (sem_t* sem);
// 销毁信号量
int sem_destroy (sem_t* sem);
练习:指针图书馆有5本《大师兄语录》,创建20个线程,每个线程去借阅这本书的阅读时间(0~10)然后还书
八、死锁
使用两把锁保护一个资源
创建:锁A、锁B
线程A 线程B
加锁A 加锁B
s1 s1
加锁B 加锁A
解锁A 解锁B
解锁B 解锁A
练习:使用互斥量实现一个死锁程序,思考如何避免死锁。
避免途径:不要连续的加锁,(其实一个资源最好只用一个锁)
九.生产者和消费者模型
一线程负责生产数据,另一部分负责消费数据
问题1:如果生产的快,消费的慢,生产者容易撑死
问题2:如果生产的慢,消费的快,消费者容易饿死
只有把问题1和问题2都处理好了才能最大限度的提高效率。
生产者快 -> 数据池满 -> 生产者休眠 -> 消费者全部开始消费 -> 数据池空 -> 消费者暂停 -> 生产者全部开始生产
十、条件变量
条件变量可以让调用线程在满足特定条件的情况下暂停。
int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);
//亦可pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 使调用线程睡入条件变量cond,同时释放互斥锁mutex
int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);
// 使调用线程睡入条件变量cond,同时释放互斥锁mutex,并在时间到了之后即使没有被唤醒,也醒过来
int pthread_cond_timedwait (pthread_cond_t* cond,
pthread_mutex_t* mutex,
const struct timespec* abstime);
struct timespec {
time_t tv_sec; // Seconds
long tv_nsec; // Nanoseconds [0 - 999999999]
};
// 从条件变量cond中唤出一个线程,
// 令其重新获得原先的互斥锁
int pthread_cond_signal (pthread_cond_t* cond);
注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,
但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
// 从条件变量cond中唤出所有线程
int pthread_cond_broadcast (pthread_cond_t* cond);
//销毁条件变量
int pthread_cond_destroy (pthread_cond_t* cond);
来源:https://www.cnblogs.com/LyndonMario/p/9429962.html