目录
一、线程概述
与进程类似,线程是允许应用程序并发执行多任务的一种机制。一个进程可以包含多个线程。同一个程序的所有线程均会独立执行相同的程序,且共享同一份全局内存区域,其中包括初始化数据段,未初始化数据段,以及堆内存段。(传统意义上的UNIX进程只是多线程程序的一个特例,该进程包含一个线程)。
并发与并行:并行需要多个CPU同时进行执行,并发就是多个任务轮流执行。
其实,在Linux底层是将线程当做进程去看。
进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。
线程是轻量级的进程LWP,在Linux环境下线程的本质仍然是进程;
查看进程的LWP号:ps -LF pid
我们在界面打开一个火狐浏览器,然后通过查看进程,然后查看线程;
然后我们关闭浏览器,其线程就会消失;
线程与进程的区别:
1、进程间的信息难以共享,由于除去只读代码段位,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
2、调用fork()来创建进程的代价相对较高,即便是利用写时复制技术,仍需要复制诸如内存页表和文件描述符表之类的多种进程的属性,这意味这fork()调用在时间上的开销依然不菲。
3、线程之间能够方便、快速地共享信息,只需要将数据复制到共享(全局或者堆)变量中即可。
4、创建线程比创建进程通常要快10倍甚至更多,线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
线程的创建,即便是写时复制,也要重现创建虚拟地址空间的,但是线程的,他们之间相互共享虚拟地址空间。只是在栈空间、text段会进行分开,每个线程都有自己特有的区域。
线程之间共享和非共享资源:
共享资源:
进程ID和父进程ID、进程组ID和会话ID、用户ID和用户组ID、文件描述符表、信号处置、文件系统的相关信息:文件权限掩码、当前工作目录、虚拟地址空间(除栈、.text外);
非共享资源:
线程ID、信号掩码、线程特有数据、error变量、实时调度策略和优先级、栈、本地变量和函数的调用链接信息。
查看当前 pthread 库版本:getconf GNU_LIBPTHREAD_VERSION
二、线程创建
一般情况下,main函数所在的线程,我们称之为主线程(main线程),其余创建的线程称之为子线程;
程序中默认只有一个进程,fork()一次会产生一个子进程。有两个进程;
程序中默认只有一个线程,pthread_creat()函数调用会创建一个线程,一个主线程一个子线程。
进程创建函数:pthread_create();
函数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
涉及的头文件:
#include <pthread.h>
功能:
创建一个子线程;
参数:
pthread_t *thread:传出参数,线程创建成功后,子线程的ID会被写到改变量中;
const pthread_attr_t *attr:attr,需要设置线程的属性,一般情况下,NULL;
void *(*start_routine) (void *):函数指针:这个函数子线程需要处理的逻辑代码;
void *arg:给第三个参数使用;
返回值:
成功:返回0;
错误:返回错误号,这个错误号和之前的error不太一样。
不能通过perror获取信息错误号,而是利用char *=strerror(int errnum);获得错误原因。
注:连接编译时需要加上-prhread;Compile and link with -pthread;
案例:
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg)
{
printf("child thread...\n");
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret!=0)
{
char * errstr=strerror(ret);
printf("error:%s\n",errstr);
}
for(int i=0;i<5;i++)
{
printf("%d\n",i);
}
return 0;
}
运行结果:
结果分析:
在结果中并没有出现我们所创建的线程中的回调函数的内容,这是因为线程创建需要时间,而且主线程可能一直牢牢占取着CPU资源,使得子线程并没有进行运行。
我们将主线程进行一定的延时;
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg)
{
printf("child thread...\n");
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret!=0)
{
char * errstr=strerror(ret);
printf("error:%s\n",errstr);
}
for(int i=0;i<5;i++)
{
printf("%d\n",i);
}
sleep(1);
return 0;
}
运行结果:
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void * callback(void *arg)
{
printf("child thread...\n");
printf("arg value :%d\n",*(int *)arg);
return NULL;
}
int main()
{
int num=10;
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,(void *)&num);
if(ret!=0)
{
char * errstr=strerror(ret);
printf("error:%s\n",errstr);
}
for(int i=0;i<5;i++)
{
printf("%d\n",i);
}
sleep(1);
return 0;
}
运行结果:
三、线程终止
线程有创建就会有终止,就如同进程一样,线程也有终止的相关API函数;
函数:
void pthread_exit(void *retval);
涉及的头文件:
#include <pthread.h>
功能:
终止一个线程,在哪个线程中调用就终止哪个线程。
参数:
retval传出参数,需要传递一个指针,最为一个返回值,可以在pthread_join()中获取到,如果不需要传出就传递NULL。
Compile and link with -pthread.
函数:
pthread_t pthread_self(void);
功能:
获取当前线程的ID;
函数:
int pthread_equal(pthread_t t1, pthread_t t2);
功能:
比较两个线程ID是否相等;
不同操作系统,pthread_t 类型的实现不一致,有的无符号的长整型,有的是使用结构体实现。
案例:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void * callback(void * arg)
{
printf("child thread id:%ld\n",pthread_self());
return NULL;
}
int main()
{
//创建一个子线程:
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret!=0)
{
char * errstr = strerror(ret);
printf("erroc:%s\n",errstr);
}
//主线程
for(int i=0;i<5;i++)
{
printf("i=%d\n",i);
}
printf("tid:%ld man thread id:%ld\n",tid,pthread_self());
//让主线程退出,当主线程退出时,他不会影响其他正常运行的线程;
pthread_exit(NULL);
printf("main thread exit\n");
return 0;
}
运行结果:
结果分析:
在主线程中创建了一个子线程,然后执行主线程和子线程的程序,并在主线程和子线程中获取对应的线程ID,在主线程中采用线程退出函数。主线程虽然退出了,但是整个进程并没有结束,等到所有线程都退出后,进程才结束。
四、连接已终止的线程
涉及头文件 :
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval);
功能:
和一个已经终止的线程进行连接。 回收子线程的资源。
多个子线程结束后如果没被回收,会形成僵尸线程。
关于进程和线程关于终止进程线程回收的区别:进程只能由父进程进行回收(父进程结束,由进程号为1的进程进行领养),而线程是任意线程都可以回收终止的线程,线程使用的就是pthread_join函数。
连接的含义就是连接后,对函数进行一个释放。
特点:
这个函数是阻塞函数,调用一次只能回收一个子线程。
一般在主线程中使用。但是在其他线程中也可以使用;
参数:
pthread_t thread——需要回收子线程的ID。
retval:接收子线程退出时的值。
返回:
成功返回0;非0失败,返回错误号。
案例:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
int value=10;
void * callback(void * arg)
{
printf("child thread id:%ld\n",pthread_self());
// sleep(3);
//return NULL;
// int value=10;//局部变量
pthread_exit((void *)&value);
}
int main()
{
//创建一个子线程:
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret!=0)
{
char * errstr = strerror(ret);
printf("erroc:%s\n",errstr);
}
//主线程
for(int i=0;i<5;i++)
{
printf("i=%d\n",i);
}
printf("tid:%ld man thread id:%ld\n",tid,pthread_self());
//主线程调用pthread_jion()回收子线程的资源。
int * thread_retval;
ret = pthread_join(tid, (void *)&thread_retval);
if(ret!=0)
{
char * errstr = strerror(ret);
printf("erroc:%s\n",errstr);
}
printf("exit data :%d\n",* thread_retval);
printf("回收子线程资源成功!\n");
//让主线程退出,当主线程退出时,他不会影响其他正常运行的线程;
pthread_exit(NULL);
return 0;
}
运行结果:
五、线程分离
涉及的头文件:
#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread);
功能:
分离一个线程,将线程标记为分离线程,在分离线程终止时,它的资源会被自动释放不需要与终止线程再进行连接
注意:1、一个线程只能分离一次,不能多次分离。会产生不可预料的行为;
2、不能去连接一个已经分离的线程;会报错;
参数:
需要分离的线程的ID;
返回值:
成功返回0;失败返回错误号;
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg)
{
printf("child thread id:%ld\n",pthread_self());
return NULL;
}
int main()
{
//1、创建一个子线程
pthread_t tid;
int ret= pthread_create(&tid,NULL,callback,NULL);
if(ret!=0)
{
char * errstr= strerror(ret);
printf("error1:%s\n",errstr);
}
//输出主线程和子线程的ID
printf("tid:%ld,main thread id :%ld\n",tid,pthread_self());
//设置子线程分离,子线程分离后,子线程结束时,子线程的资源就不要主线程释放;
//设置分离后,对子线程进行连接
ret = pthread_detach(tid);
if(ret!=0)
{
char * errstr= strerror(ret);
printf("error2:%s\n",errstr);
}
ret = pthread_join(tid,NULL);
if(ret!=0)
{
char * errstr= strerror(ret);
printf("error3:%s\n",errstr);
}
pthread_exit(NULL);
return 0;
}
运行结果:
线程分离后不需要在用过其他线程在对其进行一个线程连接,如果再链接就会得到失败,我们案例中检测,返回了失败的原因。
六、线程取消
涉及头文件:
#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread);
功能:
取消线程(让线程终止)
取消某个线程,可以终止某个线程的运行,但不是立马终止;
而是当子线程执行到一个取消点,线程才会终止,
取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换。
案例:
#include <stdio.h>
#include <pthread.h>
void * callback(void * arg)
{
printf("child thread id:%ld\n",pthread_self());
for(int i=0;i<5;i++)
{
printf("chaild:%d\n",i);
}
return NULL;
}
int main()
{
//1、创建一个子线程
pthread_t tid;
int ret= pthread_create(&tid,NULL,callback,NULL);
//取消线程
pthread_cancel(tid);
for(int i=0;i<5;i++)
{
printf("%d\n",i);
}
//输出主线程和子线程的ID
printf("tid:%ld,main thread id :%ld\n",tid,pthread_self());
pthread_exit(NULL);
return 0;
}
运行结果:
每次运行结果是不同的,线程取消不是立刻就取消的,而是到了取消点(printf)才会取消。
七、线程属性
相关函数:
int pthread_arr_init(pthread_attr_t *attr);
初始化线程属性变量
int pthread_attr_destroy(pthread_attr_t *attr);
释放线程属性的资源;
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
获取线程分离的状态属性;
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
设置线程分离的状态属性
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg)
{
printf("child thread id:%ld\n",pthread_self());
return NULL;
}
int main()
{
//创建一个线程属性变量
pthread_attr_t attr;
//初始化属性变量
pthread_attr_init(&attr);
//设置属性;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//获取线程的栈大小
size_t size;
pthread_attr_getstacksize(&attr,&size);
printf("%ld\n",size);
//1、创建一个子线程
pthread_t tid;
int ret= pthread_create(&tid,&attr,callback,NULL);
if(ret!=0)
{
char * errstr= strerror(ret);
printf("error1:%s\n",errstr);
}
//输出主线程和子线程的ID
printf("tid:%ld,main thread id :%ld\n",tid,pthread_self());
//设置子线程分离,子线程分离后,子线程结束时,子线程的资源就不要主线程释放;
//释放线程属性资源
pthread_attr_destroy(&attr);
pthread_exit(NULL);
return 0;
}
运行结果:
获取线程栈的大小, 创建一个线程属性变量,初始化属性变量,设置属性,获取线程的栈大小。