前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程的概念及linux下线程库相关函数的使用

线程的概念及linux下线程库相关函数的使用

作者头像
lexingsen
发布2022-02-24 15:34:50
5400
发布2022-02-24 15:34:50
举报
文章被收录于专栏:乐行僧的博客乐行僧的博客

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 %d\n",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("%s\n",strerror(res));//打印错误信息
    }
    int i = 0;
    for(;i< 5;++i)
    {
        printf("parent pthread %d\n",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:%lu\n",pthread_self());
    return NULL;
}

int main()
{
    pthread_t pthid;
    int res = pthread_create(&pthid,NULL,fun,NULL);
    if(res != 0)
    {
        printf("%s\n",strerror(res));
    }
    printf("主线程id:%lu\n",pthread_self());
    sleep(1);
    return 0;
}
这里写图片描述
这里写图片描述

注意:在使用gcc进行编译的时候需要加库名,否则会出先链接错误。因为线程库头文件仅仅包含了函数的声明,函数的实现在哪里编译器是不知道。如果不加库名,会出现如下的链接错误。

这里写图片描述
这里写图片描述

4.3单个线程退出

函数原型: void pthread_exit(void *retval) 参数:retval表示线程的退出状态,通常穿NULL。当要求传出具体的退出状态时,可以使用retval。

当使用exit函数退出线程时,存在的问题是如果当前还有线程没有执行相应的任务,但是由于进程的退出,强制使得线程被迫退出。因为线程依赖与进程这是非常危险的退出方式,因此提出来了单线程的退出。不会影响到其他线程的撤销以及进程的撤销。

代码语言:javascript
复制
#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 = %d\n",i);
    }
    return NULL;
}

int main()
{
    pthread_t pthid;
    int res = pthread_create(&pthid,NULL,fun,NULL);
    if(res != 0)
    {
        printf("%s\n",strerror(res));
    }
    int i = 0;
    for(i = 0;i < 5;++i)
    {
        printf("parent pthread i = %d\n",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 = %lu\n",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 = %lu\n",pthread_self());
    printf("%s\n",str);
    return 0;
}

执行的结果如下:

这里写图片描述
这里写图片描述

主线程阻塞,等待子线程退出。获取子线程的退出状态并输出。

以上即线程的相关概念以及Linux系统下线程库相关重要的函数具体应用,大家也可以自行举例,验证函数。进一步的去理解线程的真正意义以及如何使用线程相关的开发。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档