线程控制和线程资源保护

来源:互联网 发布:Ubuntu 安装 login 编辑:程序博客网 时间:2024/05/16 08:22

第十五篇 线程控制和线程资源保护

1、引言
1)从前面的课程我们知道,每个进程都有自己的进程空间(也称为虚拟内存空间),并且在内核中每
个进程都有属于自己的task_struct进程表项来描述自己,这个表项中包含一个很重要的东西,那
就是虚拟地址与实际物理地址的映射表。
2)每个进程的空间的范围都是0到4G,而且都是自己私有的,进程间的切换,会导致虚拟空间的
切换,需要os+mmu一起实现,进程切换会导致TLB更新新的映射表,而且cahce里面的内容也会更
新。
除此外还需要对被切换进程的状态进行保存,所以实际上进程间实现切换的开销(时间和空间)是非常大的,所以利用多进程实现多任务是比较费力的事情,但是又必须需要进程,因为它需要作为资源分配的最小单元而存在。正是由于进程的这些的缺点,本篇我们正式引入线程的概念。
2、线程(也称为轻量级的进程)
2.1、线程基本特点
a)线程依赖于进程而存在,在一个进程里面开出的多个线程共享同一进程空间,这样利用 多
线程实现多任务时,就能避免因为大量频繁的进程间切换而带来的开销。
b)每个线程也同样有一个task_truct结构体来描述,但是共享同一进程的所有线程共享入口
地址
c)进程与线程都参与统一的内核调度(切换)。
d)线程的本质就是指针函数。
2.2、线程好处
1)大大提高了进程间的切换效率
2)避免了不必要的TLB和cache刷新,节省了空间的消耗
2.3、线程的实现(线程控制函数)
1)不由内核实现,而是由第三方线程库实现的,编译时需要连接才行
2)同一进程内的所有线程都会共享进程的哪些资源呢?
a)指令代码
b)静态数据(主要是用于线程间通信用的全局变量)
c)进程打开的所有的文件描述符
d)进程的当前工作目录
e)进程的实际用户ID和有效用户ID
f)实际组ID和有效组ID
3)每个线程的私有资源有哪些?
a)线程ID(TID)
b)自己的PC(程序计数器值)和相关的状态寄存器(存放该线程被切换时的状态)
c)自己的局部变量(每个线程其实就是一个函数),每个线程都会从进程栈中开出自己的 线程栈(也可称为线程函数栈)
d)自己的错误号errno
e)自己的信号掩码(屏蔽字,起初是继承进程(主线程)的),和未决信号集(继承于主线程,但是会被立即清空)
f)有自己的执行状态
4)线程实现过程
a)创建线程,pthread_create函数
b)控制线程, pthread_join,pthread_detach,pthread_cancle,pthread_exit等函数
5)线程的资源保护
我们知道,对于进程来说资源保护是天然的,因为它们拥有独立虚拟内存空间,互相不能互访,这也导致相互通信也很不容易。但是对于同一进程内的多个线程来说,由于它们共享进程的虚拟内存地址,因此它们相互之间可以很容易地进行资源的共享,但是资源间的保护却成为了一个问题。线程资源保护机制有:
a)无名信号量(进程用的是有名信号量或称为系统V 信号量)
•互斥
•同步
b)线程间的互斥锁
互斥
c)条件变量
一种线程间的异步通信机制,类似于进程间的信号通信
6)注意,如果主线程先死,那么所有次线程都会跟着死调,所以任何线程调了exit函数后整个
进程将会结束
3、进程与线程的异同
这里写图片描述

备注:mmu + os 进行进程间的切换,进程切换的时间需要大概1000个指令周期
4、使用线程时信号的问题
1)所有线程共享信号处理方式,比如其中一个线程改变了信号处理方式后,大家都会共享这一改变
后的信号处理方式。
2)处理方式如果是捕获的话,大家共享信号的捕获函数
3)刚开始各次线程会继承主线程(进程)的信号屏蔽字,每个线程拥有属于自己的信号屏蔽字,每
个线程可以通过自己的屏蔽字任意的打开和屏蔽与自己相关的某个信号。但是每个线程的未决信号
集虽然会继承主线程的,但即便主线程的未决信号集中有味觉信号,但是在次线程中的未决信号集 会被全部清空。
4)发生的信号会被发送给任意一个未屏蔽该信号的线程,如果所有线程都屏蔽了该信号,当此信号
发生时,该信号将会设置到当前正在运行线程的未决信号集之中,谁响应了信号处理函数,该线程
将会被这个信号 捕获函数阻塞(换句话说该信号捕获函数和这个线程是一条线的运行的)
5、线程创建和取消相关的函数
a)线程创建函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

b)线程等待函数:阻塞等待线程结束,结束后由主回收线程的资源
int pthread_join(pthread_t thread, void **retval);
c)线程分离函数:分离后,资源在线程结束时自己释放,不有主线程回收

int pthread_detach(pthread_t thread);//这个函数和pthread_join二者只能有一个被用

d)主动取消线程函数

int pthread_cancel(pthread_t thread);

•取消是否是使能设置函数:默认情况是使能

int pthread_setcancelstate(int state, int *oldstate);PTHREAD_CANCEL_ENABLE:使能(default)PTHREAD_CANCEL_DISABLE:不使能

•与取消方式相关函数

    int pthread_setcanceltype(int type, int *oldtype);//获取线程取消方式函数int pthread_setcancelstate(int state, int *oldstate);//设置取消方式函数

取消方式:
•必须运行到取消点函数正常取消(调用时间长或回引起阻塞的函数都是取消点函数,但是胡
斥锁除外)
•有些阻塞函数不是取消点,导致线程长期阻塞而无法取利用
PTHREAD_CANCEL_ASYNCHRONOUS宏设置为强制取消,风险 是被取消的函数返回
状态是未定义的, PTHREAD_CANCEL_DEFERRED默 认是按照取消点取消,这个不
设置也可以
•因为没有取消点而无法取消,但又不想设置为强制取消void pthread_testcancel(void),认为设
置一个可取消点
e)线程主动退出函数
1、return
2、void pthread_exit(void *retval); 这两个函数有区别,区别后面讲
f)线程退出处理函数的设置
1、注册线程退出处理函数(函数压栈)

void pthread_cleanup_push(void (*routine)(void *), void *arg);

2、线程退出处理函数弹栈(参数非0就弹栈),出栈顺序与入栈顺序相反

void pthread_cleanup_pop(int execute);//

3、需要了解的集中情况
•以上两个函数必须配对使用
•线程被被别人调用pthread_cancel函数取消(这种情况下返回值为-1),这时即便 thread_cleanup_pop函数没有得到调用,也将会弹栈
•线程主动调用pthread_exit函数退出,这时同样是即便thread_cleanup_pop函数没有得到调用,也会弹栈。但是return函数不会,这就是pthread_exit和return之间的区别
•主动的调用thread_cleanup_pop(!0),当参数是非0时就会弹栈,一般线程return返回时,
这个函数必须写在return的前面,只有pop函数得到调用后才会弹栈
g)获取线程id(是个非负数)的函数,%u的形式打印出来
pthread_self(void);
6、线程属性设置函数(非教学既定内容,了解)
1)线程属性有哪些:
a)绑定属性:用户线程和内核服务线程是否绑定(default:非绑定),cpu时间片的调度是面向 内核线程实现的
b)分离属性:线程结束时,线程资源由谁回收,设置了分离,就由自己释放,未设置分离,资 源由等待线程(join函数等待)回收,(default:非分离),分离属性也可由前面的 pthread_detach函数来设置
c)堆栈的地址和堆栈的大小:线程的堆栈其实就是函数的堆栈,是从进程的堆栈中刮分出来的, 默认情况下,线程具体的堆栈地址由内核决定,堆栈大小默认1M,当然我也可以认为的更改它
d)线程的优先级:由于线程是与它的生父进程(主线程)一样统一地参与内核的 调度器的调度,所以存在着调度优先级的问题,默认情况是他和生父进程有着同样的优先级别
e)一般情况下,我们都采用默认设置即可

2)线程属性设置函数
a)线程属性初始化函数

int pthread_attr_init(pthread_attr_t *attr);

b)线程属性删除函数:删除为属性设置,恢复内核原有默认的属性设置,
防止永远的篡改了默认的属性设置

int pthread_attr_destroy(pthread_attr_t *attr);

c)设置绑定函数

int pthread_attr_setscope(pthread_attr_t *attr, int scope);//设置int pthread_attr_getscope(pthread_attr_t *attr, int *scope);//获取

d)设置分离属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);//获取

e)设置堆栈属性

/* 设置 */int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize)/*获取*/int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize)

f)设置优先级属性

            /* 设置 */int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);/* 获取 */int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);

g)其它函数等:略
7、设置互斥锁相关的函数
1)互斥锁初始化函数

 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

2)互斥锁直接初始化,只能初始化,不能赋值,因为pthread_mutex_t是结构体类型

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

3)互斥锁销毁函数

int pthread_mutex_destroy(pthread_mutex_t *mutex);

4)加锁解锁函数
•阻塞解锁:int pthread_mutex_lock(pthread_mutex_t *mutex);
•非阻塞加锁:int pthread_mutex_trylock(pthread_mutex_t *mutex);
•解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);
5)互斥锁属性设置函数(非教学内容,了解)
•互斥锁属性初始化和删除

/* 属性初始化 */int pthread_mutexattr_init(pthread_mutexattr_t *attr);   /* 互斥所属性删除 :类似线程的属性删除 */        int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

•互斥锁类型设置和获取
互斥锁类型分类:
a)PTHREAD_MUTEX_DEFAULT:快速互斥锁(或叫阻塞互斥锁),默认此种锁,统一 把锁,不能多次加锁,已经解开了的锁也不能再次解锁,这些 都会出错返回
b)PTHREAD_MUTEX_ERRORCHECK:检错互斥锁,快速互斥锁的费阻塞版本
c)PTHREAD_MUTEX_RECURSIVE:递归互斥锁,同一把锁可多次枷锁,每加一次锁, 锁的连接计数加1,解锁时的解锁顺序与加锁顺序相反,每解一次连接技术减1,加过 多少次锁,就必须接多少次

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);//设置
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);//获取

•其它还有大概十几个函数,这里省略
7、设置线程信号量相关的函数
1)初始化函数

int sem_init(sem_t *sem, int pshared, unsigned int value);

2)删除函数

int sem_destroy(sem_t *sem);

3)P操作:等资源

int sem_wait(sem_t *sem);//阻塞等待资源int sem_trywait(sem_t *sem);//非阻塞等待资源/*  可设置超时,阻塞超过一定时间,超时不在阻塞 */int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

4)v操作:释放资源

int sem_post(sem_t *sem);

•信号量无属性设置
8、条件变量设置
1)初始化和删除函数

/* 初始化条件变量 */int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);//删除条件变量pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//直接变量初始化(不能赋值)

2)条件变量发送信号函数

/* 唤醒全部因为该条件变量而休眠的线程 */int pthread_cond_broadcast(pthread_cond_t *cond);/* 唤醒其中一个因为该条件变量而休眠的线程 */int pthread_cond_signal(pthread_cond_t *cond);

3)等待条件变量信号函数

/* 可设置超时,等待超时,则超时返回 */        int pthread_cond_timedwait(pthread_cond_t *restrict cond, \pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);/* 一直等待,知道到等到唤醒信号 */int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

•属性设置函数(课程外内容)
1) 属性初始化和删除

int pthread_condattr_destroy(pthread_condattr_t *attr);int pthread_condattr_init(pthread_condattr_t *attr);

2)属性设置获取函数

/* 获取条件变量属性 */int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int        *restrict pshared);/* 设置条件变量属性 */int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

•PTHREAD_PROCESS_SHARED:条件变量能够在不同进程的线程之间使用
•PTHREAD_PROCESS_PRIVATE:只能在本进程的线程之间使用

0 0
原创粉丝点击