1.线程的概念 在linux操作系统下,线程的本质任然是进程。是轻量级的进程(light weight process)简称LWP,但线程与进程还是有很多的区别。
1.1为什么要引入线程,线程相对于进程优势在哪里? 历史回顾:在20世纪90年代,由于多处理系统的迅速发展。提出了比进程更小且能够独立运行的单位——线程,以提高系统内程序并发执行的程度,改善操作系统的性能。
创建进程时,需要为其分配资源,并建立进程控制块pcb;撤销进程时,系统需要回收分配给进程的资源以及释放进程控制块,而当切换进程时,需要保护当前进程的上下文,并为切换的进程提供cpu执行环境。实际操作系统的开销比较大。 正是由于在进程切换时,操作系统的开销比较大。在操作系统中所设置的进程数量不能过多,否则在一定程度上降低了操作系统的并发程度。
引入线程的目的有二: (1).以较低的开销来提高操作系统的并发程度。 (2).简化进程间通讯。
2.线程与进程的对比
2.1线程 可在cpu上运行的基本执行单位。 进程内的一个代码片段可以被创建为一个线程。 线程状态:运行、就绪和等待。 线程操作:创建、撤销、等待和唤醒等。 进程依旧是资源分配的最小单位。 线程自己不用有系统资源,通过进程申请资源。
2.2进程 重型线程。 只有一个主线程。 单线程的模型。
对比图
2.3线程结构 指令和数据来源于进程。 各类资源来源于进程。 相对于进程的进程控制块线程有线程控制块:包含栈空间、寄存器集、程序计数器和线程的ID。
3.线程的优点及缺点 优点:1.提高程序的并发程度 2.系统开销小 3.数据共享,通信方便。 缺点:1.库函数,不稳定 2.调试编写困难,gdb不支持调试。 3.对信号支持不好
4.linux线程库中相关函数的使用。
头文件:#include<pthread.h>
4.1创建线程 函数原型:
代码语言:javascript复制int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void* (*start_routine)(void *), void *arg))
功能:创建一个线程。
返回值:成功创建返回值为0,错误返回错误号。注意:由于创建线程函数是一个库函数,不是系统调用函数。所以其错误信息不能用perror()进行打印,采用strerror(错误号)
可以将错误信息打印出来。其中strerror
函数是包含#include<string.h>
之中的一个库函数。
参数: 参数1:是一个传出参数,用于保存成功创建线程之后对应的线程id。 参数2:表示线程的属性,通常默认传NULL,如果想使用具体的属性也可以修改具体的参数。 参数3:函数指针,一个指向函数的指针。指向创建线程所执行函数的入口地址,函数执行完毕,则线程结束。 参数4:线程主函数执行期间所使用的参数。 下面举例使用以上函数:
代码语言:javascript复制#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<sys/types.h>
void* my_fun(void *arg)
{
int i = 0;
for(;i < 5;i )
{
printf("child pthread %dn",i);
}
return NULL;
}
int main()
{
pthread_t pthid;//传出参数,保存创建成功后的线程id
int res = pthread_create(&pthid,NULL,my_fun,NULL);
//参数2默认为NULL,指的是线程的属性
//函数指针,指向创建出线程的主函数
//线程主函数的参数,就是void* my_fun(void *arg)中的void *arg
if(res != 0)
{
printf("%sn",strerror(res));//打印错误信息
}
int i = 0;
for(;i< 5; i)
{
printf("parent pthread %dn",i);
}
//由于程序执行的速度非常快,子线程还没来得及执行。进程已经结束
//主线程睡眠两秒,使得子线程可以执行完毕
sleep(2);
return 0;
}
4.2获取线程id 函数原型:
代码语言:javascript复制pthred_t pthread_self(void);
功能:获取当前线程的id。 参数:无参。
返回值:返回值为一个无符号长整型。
代码语言:javascript复制#define pthread_t unsigned long int
说明:线程id是在一个进程中的内部标识,但不同进程中的线程id可能相同。、 举例:
代码语言:javascript复制#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<pthread.h>
void* fun(void *arg)
{
printf("子线程id:%lun",pthread_self());
return NULL;
}
int main()
{
pthread_t pthid;
int res = pthread_create(&pthid,NULL,fun,NULL);
if(res != 0)
{
printf("%sn",strerror(res));
}
printf("主线程id:%lun",pthread_self());
sleep(1);
return 0;
}
注意:在使用gcc进行编译的时候需要加库名,否则会出先链接错误。因为线程库头文件仅仅包含了函数的声明,函数的实现在哪里编译器是不知道。如果不加库名,会出现如下的链接错误。
4.3单个线程退出
函数原型: void pthread_exit(void *retval)
参数:retval表示线程的退出状态,通常穿NULL。当要求传出具体的退出状态时,可以使用retval。
当使用exit
函数退出线程时,存在的问题是如果当前还有线程没有执行相应的任务,但是由于进程的退出,强制使得线程被迫退出。因为线程依赖与进程这是非常危险的退出方式,因此提出来了单线程的退出。不会影响到其他线程的撤销以及进程的撤销。
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<pthread.h>
void* fun(void *arg)
{
int i = 0;
for(;i < 3; i)
{
printf("child pthread i = %dn",i);
}
return NULL;
}
int main()
{
pthread_t pthid;
int res = pthread_create(&pthid,NULL,fun,NULL);
if(res != 0)
{
printf("%sn",strerror(res));
}
int i = 0;
for(i = 0;i < 5; i)
{
printf("parent pthread i = %dn",i);
}
//sleep(1);//若不加sleep(1)时,由于主线程执行的太快。
//子线程还没来得及执行,进程结束子线程被迫结束
pthread_exit(NULL);//退出当前的线程,并没结束整个进程。
//只有当进程中所有线程执行完毕后,进程才会结束
return 0;
}
没有添加单线程退出函数的结果:
和我们预期的结果是一致的。
添加单线程退出函数的执行结果:
可见,单线程退出函数确实起到了作用。
4.4阻塞等待线程退出,回收线程的资源。
函数原型:int pthread_join(pthread_t thread, void **retval)
参数: pthread为线程id,retval为线程的状态。可以与pthread_exit()
结合使用。
调用该函数的线程将挂起等待,为阻塞的状态。直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下: 1.如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。 2.如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。 3.如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。 4.如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。 举例:
代码语言:javascript复制#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<pthread.h>
#include<assert.h>
void* myfun(void *arg)
{
printf("child pthread id = %lun",pthread_self());
char *str = "子线程的退出状态!n"
pthread_exit(str);
return NULL;
}
int main(void)
{
pthread_t pthid;
int res = pthread_create(&pthid,NULL,myfun,NULL);
assert(res == 0);
char *str = NULL;
pthread_join(pthid,&str);
printf("parent pthread id = %lun",pthread_self());
printf("%sn",str);
return 0;
}
执行的结果如下:
主线程阻塞,等待子线程退出。获取子线程的退出状态并输出。
以上即线程的相关概念以及Linux系统下线程库相关重要的函数具体应用,大家也可以自行举例,验证函数。进一步的去理解线程的真正意义以及如何使用线程相关的开发。