有了进程,为什么要引入线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
1、进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2、进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。
现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。
创建线程之后,地址空间没有变化,进程退化成了线程(主线程),创建出的子线程和主线程共用地址空间,主线程和子线程有各自独立的pcb,子线程的pcb是从主线程拷贝来的
主线程和子线程共享:
.text、.bss、.data、堆、动态库加载区、环境变量、命令行参数
他们之间的通信可以用:全局变量,堆
不共享:
不共享栈区,如果有2个线程的话,栈区被平均分2份。
在linux下:线程就是进程-轻量级进程,对于内核来说,线程就是进程
查看指定线程的LWP号:
线程号和线程ID是有区别的
线程号是给内核看的
多线程和多进程的区别:
多进程始终共享的资源:代码,文件描述符,内存映射区(mmap)
多线程共享的资源:堆、全局变量,线程节省资源
1、创建线程
#include
int pthread_create(
pthread_t *thread,//线程id=无符号长整型
const pthread_attr_t *attr,//线程属性,NULL
void *(*start_routine) (void *),//线程处理函数
void *arg//线程处理函数的参数
);
参数:
thread:传出参数,线程创建成功之后,会被设置一个合适的值
attr:默认NULL
start_routine:子线程的处理函数
arg:回调函数的参数
RETURN VALUE
On success, pthread_create() returns 0; on error, it returns an error number, and the contents
of *thread are undefined.
#include
#include
#include
#include
#include
#include
#include //线程头文件
void* myfunc(void * arg){
//打印子线程的id
int num=*(int*)arg;
printf("%d child thread id:%lu\n",num,pthread_self());
return NULL;
}
int main(int argc,const char * argv[]){
//创建一个子线程
//线程id变量
pthread_t pthid[5];
for(int i=0;i<5;i++){
//第4个参数是传递的是地址
pthread_create(&pthid[i],NULL,myfunc,(void *)&i);
//pthread_create(&pthid[i],NULL,myfunc,(void *)i);
}
printf("parent thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("i=%d\n",i);
}
sleep(1);
return 0;
}
我们想要的结果是什么呢?
0 child thread id:xxxx
1 child thread id:xxxx
4 child thread id:xxxx
但是实际情况又是什么呢?
那么为什么会出现这种情况呢?
是这样的,当i==0的时候,把第一个线程创建出来,然后在执行这条语句之前,
printf("%d child thread id:%lu\n",num,pthread_self());
线程失去的cpu,当i==1的时候,把第二个线程创建出来,然后如果这时候,第一个线程得到cpu的话,就会打印,因为往里传递的是i的地址.这个时候 i为1会本想打印 0 child thread id:xxxx,打印成了1 child thread id:xxxx.
如何避免这种情况呢?
不传递i的地址,传递i的值就可以了将
pthread_create(&pthid[i],NULL,myfunc,(void *)&i);
改为
pthread_create(&pthid[i],NULL,myfunc,(void *)i);
然后
int num=*(int*)arg;
//改为
int num=(int)arg;
2、单个线程的退出
1、exit(0)
2、pthread_exit
#include
void pthread_exit(void *retval);
//retval指着必须指向全局,堆,不能是栈
//pthread_join可以获取
#include
#include
#include
#include
#include
#include
#include
//线程头文件
void* myfunc(void * agr){
//打印子线程的id
printf("child thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("child i=%d\n",i);
if(i==2){
//exit(1);//也可以
int number=100;
pthread_exit(&number);
//return NULL;//也可以
}
}
return NULL;
}
int main(int argc,const char * argv[]){
//创建一个子线程
//线程id变量
pthread_t pthid;
int ret=pthread_create(&pthid,NULL,myfunc,NULL);
if(ret!=0){
printf("error number:%d\n",ret);
char * error=strerror(ret);
printf("error info %s\n",error);
}
printf("parent thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("parent i=%d\n",i);
}
//退出主线程
pthread_exit(NULL);
return 0;
}
3、阻塞等待线程退出,获取线程退出状态—pthread_join
#include
int pthread_join(pthread_t thread, void **retval);
参数:
thread:要回收的子线程的线程id
retval:读取线程退出的时候携带的状态信息
传出参数
void *ptr;
pthread_join(pthid,&ptr);
指向的内存和pthread_exit参数指向同一块地址
#include
#include
#include
#include
#include
#include
#include //线程头文件
//int number=100;//全局变量可以
void* myfunc(void * agr){
//打印子线程的id
printf("child thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("child i=%d\n",i);
if(i==2){
//exit(1);
int * number=(int *)malloc(sizeof(int));//堆变量也可以
*number=100;
pthread_exit(number);
//return NULL;//也可以
}
}
return NULL;
}
int main(int argc,const char * argv[]){
//创建一个子线程
//线程id变量
pthread_t pthid;
int ret=pthread_create(&pthid,NULL,myfunc,NULL);
if(ret!=0){
printf("error number:%d\n",ret);
char * error=strerror(ret);//错误信息,这里并不是用perror
printf("error info %s\n",error);
}
printf("parent thread id:%lu\n",pthread_self());
//阻塞等待子线程的退出,并回收pcb
void * ptr=NULL;
pthread_join(pthid,&ptr);
printf("number=%d\n",*(int*)ptr);
for(int i=0;i<10;i++){
printf("parent i=%d\n",i);
}
return 0;
}
#include
int pthread_detach(pthread_t thread);
On success, pthread_detach() returns 0; on error, it returns an error number.
调用该函数后不要pthread_join
子线程会自动回收自己的pcb
5、杀死(取消)线程–pthread_cancel
#include
int pthread_cancel(pthread_t thread);
/*
RETURN VALUE
On success, pthread_cancel() returns 0; on error, it returns a nonzero error number.
*/
注意:在要杀死的子线程对应的处理函数的内部,必须做过一次系统调用
write read printf都有系统调用,
int a;
a=2;
int b=a+3;
//以上没有系统调用
#include
#include
#include
#include
#include
#include
#include
//线程头文件
void* myfunc(void * agr){
//打印子线程的id
while(1){
int i,j,k;
i=10;
j=20;
k=i+j;
printf("k=%d\n",k);
pthread_testcancel();//没有任何意义,就是设置一个取消点
}
return NULL;
}
int main(int argc,const char * argv[]){
//创建一个子线程
//线程id变量
pthread_t pthid;
int ret=pthread_create(&pthid,NULL,myfunc,NULL);
if(ret!=0){
printf("error number:%d\n",ret);
char * error=strerror(ret);
printf("error info %s\n",error);
}
printf("parent thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("i=%d\n",i);
}
sleep(1);
int num=pthread_cancel(pthid);
printf("num%d\n",num);
if(num!=0){
printf("error number:%d\n",num);
char * error=strerror(num);
printf("error info %s\n",error);
}
return 0;
}
6、比较两个线程ID是否相等(预留函数)
int pthread_equal(pthread_t t1,pthread_t t2);
7、线程属性类型
a.线程属性类型:pthread_attr_t attr;
b.线程属性操作函数:
//对线程属性变量的初始化
int pthread_attr_init(pthread_attr_t * attr);
//设置线程分离属性
int pthread_attr_setdetachstate(
pthread_attr_t *attr,
int detachstate
);
参数:
attr:线程属性
detachstate:
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)
c.释放线程资源函数
int pthread_attr_destroy(pthread_attr_t * attr);
#include
#include
#include
#include
#include
#include
#include
//线程头文件
void* myfunc(void * agr){
//打印子线程的id
printf("child thread id:%lu\n",pthread_self());
return NULL;
}
int main(int argc,const char * argv[]){
//创建一个子线程
//线程id变量
pthread_t pthid;
//初始化线程属性
pthread_attr_t arr;
pthread_attr_init(&arr);
//设置分离
pthread_attr_setdetachstate(&arr,PTHREAD_CREATE_DETACHED);
//创建线程的时候设置线程分离 //
int ret=pthread_create(&pthid,&arr,myfunc,NULL);
if(ret!=0){
printf("error number:%d\n",ret);
char * error=strerror(ret);
printf("error info %s\n",error);
}
printf("parent thread id:%lu\n",pthread_self());
for(int i=0;i<5;i++){
printf("i=%d\n",i);
}
sleep(2);
pthread_attr_destroy(&arr);
return 0;
}
为什么会引入线程的可分离属性呢?
主线程调用pthread_join后,如果该线程没有运行结束,主线程会被阻塞,当主线程还要创建新线程来做一些事情时,此时的主线程因为调用pthread_join而被阻塞,就没有办法来处理其他事务。所以引入了线程的可分离属性,它不需要主线程来回收它,在其退出时系统会自动回收其资源。
那用分离函数和设置分离属性有什么区别呢?
分离函数是在线程创建出来后才分离的,有可能还没分离呢,线程就死掉了。而设置分离属性,线程创建出来就是分离的。