1.线程
线程概念:linux下,线程以进程的pcb模拟实现,因此linux下的线程就是pcb,是一个轻量级进程;
简单谈一谈线程:说到线程概念,必须先说到进程。进程是一个运行中的程序,在操作系统中,一个程序运行起来,程序被加载到内存中,操作系统创建一个进程描述符(进程控制块),PCB对程序的运行进行描述控制,因此进程就是pcb,在linux是task_struct结构体。
线程与进程:
1.linux线程使用进程pcb模拟实现,因此linux线程是一个轻量级进程;如果说pcb成为了线程,那么进程就是线程组,一个进程至少有一个或多个线程。
2.因为linux下pcb是一个线程,因此线程是cpu调度的基本单位。
3.因为程序运行的时候分配进程资源,进程(线程组)是资源分配的基本单位。
一个进程中的多个线程
前提:进程中的所有线程共用一个虚拟地址空间,因此共享进程的代码段,数据段
共享数据:文件描述符表,每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
当前工作目录、用户id和组id
独有数据:因为每个线程都是pcb,是cpu调度的基本单位,因此线程可以同时运行,但是不会造成调用栈混乱,主要因为每个线程都有自己独有的数据(线程ID、寄存器、栈、errno、信号屏蔽字、调度优先级)
线程优缺点
多线程优点:因为多线程共享虚拟地址空间
1.线程间通信简单
2.线程的创建/销毁成本更低
3.线程的调度成本更低
4.线程的执行粒度更细
多线程缺点:
1.线程缺乏访问控制----exit退出的是整个进程
2.健壮性较低----线程的某些错误会导致整个进程的退出(对多个进程共享的数据做出不同的操作,导致数据出现异常)
3.性能损失----多线程共享一个处理器,若线程数量比可用的处理器数量多,可能会有较大的性能损失(性能损失指增加了额外的同步和调度开销)
4.编程难度提高----编写与调试一个多线程程序比单线程程序困难得多
2.线程创建
查看一个线程ID
ps命令中-L选项,会显示信息:
LWP:线程ID,既gettid()系统调用的返回值
NLWP:线程组内线程的个数
一个线程中有三个标识符:tid、pid、tgid
tid:线程在线程组中虚拟地址映射的地址的首地址
pid:创建一个线程的ID,就是线程ID
tgid:进程ID
[sun@ssh thread]$ cat creat.c
/*************************************************************************
> File Name: creat.c
> Author: ssh
> Mail: [email protected]
> Created Time: Fri 19 Apr 2019 09:06:42 AM CST
************************************************************************/
#include
#include
#include
#include
#include
void *thr_start(void *arg){
//pthread_t pthread_self(void);
// 返回调用线程的线程id
pthread_t tid = pthread_self();
while(1){
printf("chlid thread-----%d tig:%p\n", arg, tid);
sleep(1);
}
}
int main(int argc, char *argv[])
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine)(void *), void *arg);
// thread: 于保存返回的线程ID
// attr: 线程属性,通常置NULL
// start_routine: 线程入口函数
// arg: 给线程传入的参数
// 返回值:成功:0 失败:errno
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, (void*)666);
if(ret != 0){
printf("pthread create error\n");
return -1;
}
while(1){
printf("main thread----chlid tid:%p pid:%d\n", tid, getpid());
sleep(1);
}
return 0;
}
3.线程终止
不终止整个进程,只终止某个线程有三种方法:
1.从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2.线程可以调用pthread_exit终止自己。
3.一个线程可以调用pthread_cancel终止同一进程中的另一个线程。(指定线程退出)
/*************************************************************************
> File Name: exit.c
> Author: ssh
> Mail: [email protected]
> Created Time: Fri 19 Apr 2019 03:02:13 PM CST
************************************************************************/
#include
#include
#include
#include
#include
void *thr_start(void *arg){
//pthread_t pthread_self(void);
// 返回调用线程的线程id
pthread_t tid = pthread_self();
while(1){
printf("child thread----%d--tid:%p\n", arg,tid);
sleep(1);
//void pthread_exit(void *retval);
// 退出调用线程,并返回retval
// 线程的返回值,不仅可以判断终止场景,并且可以获取任务处理结果
pthread_exit("leihoua~~");
//eixt(0); exit退出的是整个进程(所有线程都会退出)
}
return NULL;
}
int main(int argc, char *argv[])
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr
// void *(*start_routine)(void*), void *arg);
// thread: 用于保存返回的线程ID
// attr:线程属性,通常置NULL
// start_routine: 线程入口函数
// arg: 给线程传入参数
// 返回值: 成功: 0 失败: errno
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, (void*)666);
if(ret != 0){
printf("pthread create error\n");
return -1;
}
sleep(1);
//int pthread_cancel(pthread_t thread);
// 取消指定线程---让指定线程退出
// thread:线程id
//pthread_cancel(pthread_self());
while(1){
printf("man thread-----child tid:%p pid:%d\n", tid, getpid());
sleep(1);
//pthread_exit(NULL);
}
return 0;
}
4.进程等待:等待指定线程退出---获取指定线程返回值,回收线程资源
为什么需要线程等待?
1.已经退出的线程,其空间中没有被释放,仍然在进程的地址空间内。
2.创建新的线程不会复用刚才退出线程的地址空间。
注意:一个线程被创建,默认有一个属性是joinable;处于这个属性线程必须被等待,因为线程退出后,不会自动回收资源,造成内存泄露;
/*************************************************************************
> File Name: join.c
> Author: ssh
> Mail: [email protected]
> Created Time: Fri 19 Apr 2019 03:44:47 PM CST
************************************************************************/
#include
#include
#include
#include
#include
void *thr_start(void *arg){
sleep(3);
return "abc";
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, NULL);
if(ret != 0){
printf("pthread create error\n");
return -1;
}
//int pthread_join(pthread_t thread, void **retval);
// 阻塞等待线程退出
// thread: 等待的线程ID
// retval: 用户获取线程的退出的返回值
char *retval;
pthread_join(tid, (void**)&retval);
printf("retval:%s\n", retval);
while(1){
printf("i am host thread~~\n");
sleep(1);
}
return 0;
}
5.线程分离
分离一个线程将线程的joinable属性修改为detach属性,处于这个属性的线程,退出后将自动回收资源,处于这个属性的线程,不能被等待,否则报错;
thread线程以不同的方式终止,通过pthread_join得到的终止状态时不同的:
1.如果thread线程通过return返回,value_ptr所指向的单元存放的是threa线程函数的返回值;
2.如果thread线程被别的线程调用pthread_cancel异常终止,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED;
3.如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数;
4.如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
注意:线程分离可以在任意线程任意时间分离,包括分离自己
/*************************************************************************
> File Name: join.c
> Author: ssh
> Mail: [email protected]
> Created Time: Fri 19 Apr 2019 03:44:47 PM CST
************************************************************************/
#include
#include
#include
#include
#include
void *thr_start(void *arg){
//pthread_detach(tid);
// tid:线程在线程组中虚拟地址的首地址
pthread_detach(pthread_self());
sleep(3);
return "abc";
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, NULL);
if(ret != 0){
printf("pthread create error\n");
return -1;
}
//int pthread_join(pthread_t thread, void **retval);
// 阻塞等待线程退出
// thread: 等待的线程ID
// retval: 用户获取线程的退出的返回值
/*
char *retval;
pthread_join(tid, (void**)&retval);
printf("retval:%s\n", retval);
*/
while(1){
printf("i am host thread~~\n");
sleep(1);
}
return 0;
}
6.线程安全:因为多个执行流之间对数据的竞争操作---不安全操作,有可能造成数据二义性
如何实现线程安全:同步与互斥
同步:对临界资源访问的时序可控
互斥:对临界资源同一时间的唯一访问
如何实现同步与互斥:
互斥的实现:互斥锁(mutex)
pthread_mutex_t 创建一个互斥锁变量
pthread_mutex_init() 互斥锁初始化
pthread_mutex_lock() 加锁
pthread_mutex_unlock() 解锁
pthread_mutex_destroy() 互斥锁销毁
死锁:
死锁是如何产生的:四个必要条件
互斥条件
不可剥夺条件
请求与保持条件
环路等待条件
死锁的预防:破坏必要条件------>如何破坏必要条件
死锁的避免:银行家算法,死锁检查算法 ----->链接:死锁的产生、预防、避免、检测和解除
7.条件变量:用于实现线程间同步----唤醒与等待
条件满足则唤醒等待的线程,条件不满足则等待
pthread_cond_t 创建一个条件变量
pthread_cond_init() 条件变量初始化
pthread_cond_wait() 等待
pthread_cond_signal() 唤醒
pthread_cond_destroy() 销毁
为什么pthread_cond_wait需要互斥锁?
如果有多个角色,需要使用多个条件变量分别等待,不能等待在条件变量上,有可能出现唤醒角色错误的问题。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护。没有互斥锁就无法完全的获取和修改共享数据。
8.生产者与消费者模型:一个场所,两个角色,三种关系
#include
#include
#include
#include
#include
class BlockQueue{
private:
std::queue _queue;
int _capacity;
pthread_mutex_t _mutex;
pthread_cond_t _productor;
pthread_cond_t _customer;
private:
void QueueLock(){
pthread_mutex_lock(&_mutex);
}
void QueueUnLock(){
pthread_mutex_unlock(&_mutex);
}
void ProductorWait(){
pthread_cond_wait(&_productor, &_mutex);
}
void ProductorWakeUp(){
pthread_cond_signal(&_productor);
}
void CustomerWait(){
pthread_cond_wait(&_customer, &_mutex);
}
void CustomerWakeUp{
pthread_cond_signal(&_customer);
}
bool QueueIsEmpty(){
return _queue.empty();
}
bool IsFull(){
return (_queue.size() == _capacity);
}
public:
BlockQueue(){
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_productor, NULL);
pthread_cond_init(&_customer, NULL);
}
~BlockQueue(){
pthread_mutex_destory(&_mutex);
pthread_cond_destory(&_productor);
pthread_cond_destory(&_customer);
}
void Productor(int val){
QueueLock();
if(QueueIsFull()){
ProductorWait();
}
_queue.push(val);
QueueUnLock();
CustomerWakeUp();
}
void Customer(int *val){
QueueLock();
if(QueueIsEmpty()){
CustomerWait();
}
*val = _queue.front();
_queue.pop();
QueueUnLock();
ProductorWakeUp();
}
};
9、POSIX信号量
POSIX信号量与SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的,但是POSIX可以用线程间同步。
初始化信号量:sem_init(sem_t *sem, int pshared, unsigned int value);
pshared:0表示线程间共享,非零表示进程间共享;
value:信号量初始值
销毁信号量:sem_destory(sem_t *sem);
等待信号量:sem_wait(sem_t *sem); 将信号量的值减一
发布信号量:sem_post(sem_t *sem); 表示资源使用完毕,可以归还资源
10、读写锁
使用场景:资源共享,两种操作,一种操作频繁使用,另一种操作使用的比较少;
注意:写独占,读共享,写锁优先级高
初始化:pthread_rwlock_init(pthread_rwlock_t *rwlock, (属性一般为NULL));
销毁:pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁和解锁: pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_wrlock(pthread_relock_t *rwlock);
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);