在传统操作系统上pcb是一个进程,描述一个程序的运行,还有一个tcp描述实现线程,但是在linux下使用pcb描述实现了程序调度并且这些pcb共用同一个虚拟地址空间,相较于传统的pcb更加轻量化一点,因此也把linux下的pcb称之为轻量级进程。
进程是系统资源分配的基本单位。
线程是CPU调度的基本单位。
线程间的独有与共享:
独有:栈,寄存器,信号屏蔽字,errno,标识符
共享:虚拟地址空间(代码段,数据段),文件描述表,信号处理方式,工作路径,用户ID,组ID
多进程/多线程进行多任务处理(优缺点):
多线程:线程间通信更加方便灵活(全局变量,函数传参)
线程的创建/销毁成本更低
线程间的调度成本更低
异常和某个系统调用针对的是整个进程。
多进程:具有独立性,因此更加稳定,健壮。
例如:对主功能程序安全性稳定性要求更高的最好使用多进程(shell/服务器),剩下的使用多线程
共同优点:
CPU密集型程序/IO密集型程序
并行压缩CPU处理/并行压缩IO等待时间
在CPU资源足够的情况下,可以使程序的性能更高
线程控制的接口都是库函数(操作系统并没有向用户提供创建一个轻量级进程的接口,因此大佬门才封装的一套线程控制接口,所以在使用的时候链接库文件 -lpthread)
int pthread_create(pthread_t tid,pthread_attr_t attr,void(thread_routine)(voidarg),void arg);**
tid:用于获取线程id,通过这个id可以找到线程的描述信息,进而访问pcb(轻量级进程完成控制)【线程在虚拟地址空间中开辟的线程空间的首地址(线程信息)】
attr:线程属性,通常置NULL
thread_routine:线程入口函数,创建一个线程就是为了运行这个函数,函数运行完毕,则线程退出
arg:通过线程入口函数传递给线程的参数
返回值:0-成功,非0值-失败errno
ps -efL | grep create:查看线程
代码演示:
#include
#include
#include
#include
void* thread_start(void* arg){
while(1){
printf("主线程传递了一个参数:%s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;//无符号长整形
char buf[]="好运来~\n";
int ret=pthread_create(&tid,NULL,thread_start,(void*)buf);
if(ret!=0){
printf("thread create error:%d\n",ret);
return -1;
}
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
1.普通线程入口函数中的return
注意:main函数中的return退出的是进程
2.void pthread_exit(viod retval);//退出一个线程,谁调用谁退出*
retval-线程返回值
代码演示:
#include
#include
#include
#include
void* thread_start(void* arg){
sleep(5);
pthread_exit(NULL);
while(1){
printf("主线程传递了一个参数:%s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;//无符号长整形
char buf[]="好运来~\n";
int ret=pthread_create(&tid,NULL,thread_start,(void*)buf);
if(ret!=0){
printf("thread create error:%d\n",ret);
return -1;
}
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
3.int pthread_cancel(pthread_t tid);//退出指定的线程
tid:就是指定的线程id
代码演示:
#include
#include
#include
#include
void* thread_start(void* arg){
while(1){
printf("主线程传递了一个参数:%s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;//无符号长整形
char buf[]="好运来~\n";
int ret=pthread_create(&tid,NULL,thread_start,(void*)buf);
if(ret!=0){
printf("thread create error:%d\n",ret);
return -1;
}
sleep(3);
pthread_cancel(tid);
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
注意
1.线程退出不会完全释放资源,需要被其他线程等待。
2.取消自己是一种违规操作(主线程退出)。
3.主线程退出,并不影响整个进程的运行,只有所有的线程退出,进程才会退出。
等待一个线程的退出,获取退出线程的返回值,回收这个线程所占用的资源。
int pthread_join(pthread_t tid,void** retval);
tid:指定要等待的线程id
retval:用于获取线程退出返回值
代码演示:
#include
#include
#include
#include
void* thread_start(void* arg){
char* buf="我是锦鲤\n";
sleep(5);
pthread_exit(buf);
while(1){
printf("主线程传递了一个参数:%s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;//无符号长整形
char buf[]="好运来~\n";
int ret=pthread_create(&tid,NULL,thread_start,(void*)buf);
if(ret!=0){
printf("thread create error:%d\n",ret);
return -1;
}
void* retval=NULL;
pthread_join(tid,&retval);
printf("retval:%s\n",retval);
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
不是所有的线程都能被等待,一个线程被创建默认情况下有一个属性-joinable;处于joinable属性的线程退出后,不会自动释放资源,需要被等待。
将线程的属性从joinable设置为detach;处于detach属性的线程退出后会自动释放资源,不需要被等待。
等待一个被分离的线程,则pthread_join会返回错误:这不是一个joinable线程(因为在获取返回值的时候将获取不到,detach属性的线程退出后已经自动释放了资源)
int pthread_detach(pthread_t tid);//分离指定的线程
线程的分离可以在任意地方,可以在线程入口函数中让线程分离自己,也可以让创建线程在创建之后直接分离。
在多个执行流中对同一个临界资源进行访问,而不会造成数据二义。
通过一些条件判断来实现多个执行流对临界资源访问的合理性(有资源则访问,没有资源则等着,等有资源了再被唤醒)。
条件变量:
1.向用户提供两个接口:使一个线程等待的接口和唤醒一个线程的接口
2.等待队列
posix标准的信号量:
通过保证同一时间只有一个执行流可以对临界资源进行访问(一个执行流访问期间,其他执行流不能访问),来保证数据访问的安全性。
互斥锁:用于标记当前临界资源的访问状态
计数器:0/1 0-表示不可访问 1-表示可以访问
每一个线程访问临界资源之前,先判断计数,当前临界资源的状态(是否有人正在访问–正在访问的线程将状态置为了0(不可访问状态))
1.第一个线程访问的时候,判断可以访问,因此将状态置为不可访问,然后去访问资源
2.其他线程访问的时候,发现不可访问,就陷入等待(可中断休眠状态)
3.第一个线程访问临界资源完毕后,将状态置为1(可以访问)唤醒(将pcb状态置为运行态)等待线程,大家重新开始竞争这个资源
多个执行流在对多个锁资源进行争抢操作,但是因为推进顺序不当,而导致互相等待,流程无法继续推进的情况。
死锁产生的四个必要条件:
1.互斥条件:一个锁只有一个人能加,我加了锁,其他人就不能加了
2.不可剥夺条件:我加的锁,别人不能替我释放
3.请求与保持条件:我加了A锁,然后请求B锁,但是请求不到B锁,我也不释放A锁
4.环路等待条件:我拿着A锁请求B锁,对方拿着B锁请求A锁
预防死锁:破坏产生死锁的四个必要条件
1.锁资源按顺序一次性分配
2.加锁的时候可以使用非阻塞加锁,若无法加锁,则将手中的其他锁释放
避免死锁:
银行家算法:
定义两种状态:安全/非安全-
现在有多少资源
现在那些人已经借了多少钱
当前还有那些人需要借多少钱
若给一个执行流分配指定的锁有可能会造成环路等待(非安全状态),则不予分配,并且回溯释放当前执行流已有的资源
少量写临界资源的执行流+大量读临界资源的执行流
特性:不能同时写,但是可以同时读(写互斥,读共享),写的时候别人既不能写,也不能读;但是读的时候大家可以一起读,但是读的时候不能写
读写锁的实现:
两个计数器:读者计数/写者计数
读者计数:>0,表示当前有人正在读,想要加写锁的人就需要等待,而想要加读锁可以继续加
写者计数:>0,表示当前有人正在写, 想要加写锁的人就需要等待,想要加读锁也需要等待
其中不能加锁时的等待,通过自旋锁实现
自旋锁:循环判断条件,并且强占CPU(一直处于运行状态,判断条件,CPU不可剥夺),比较消耗CPU,比较适合于等待时间比较短的操作