Linux多线程

有了进程,为什么要引入线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

1、进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

2、进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。

现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。
Linux多线程_第1张图片
创建线程之后,地址空间没有变化,进程退化成了线程(主线程),创建出的子线程和主线程共用地址空间,主线程和子线程有各自独立的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
但是实际情况又是什么呢?
Linux多线程_第2张图片
那么为什么会出现这种情况呢?

是这样的,当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;
}

Linux多线程_第3张图片
4、线程分离

#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而被阻塞,就没有办法来处理其他事务。所以引入了线程的可分离属性,它不需要主线程来回收它,在其退出时系统会自动回收其资源。

那用分离函数和设置分离属性有什么区别呢?
分离函数是在线程创建出来后才分离的,有可能还没分离呢,线程就死掉了。而设置分离属性,线程创建出来就是分离的。

你可能感兴趣的:(Linux)