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

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线程库中相关函数的使用。

扫描二维码关注公众号,回复: 952042 查看本文章

头文件:#include<pthread.h>

4.1创建线程
函数原型:

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:线程主函数执行期间所使用的参数。
下面举例使用以上函数:

#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 = 0for(;i< 5;++i)
    {
        printf("parent pthread %d\n",i);
    }
    //由于程序执行的速度非常快,子线程还没来得及执行。进程已经结束
    //主线程睡眠两秒,使得子线程可以执行完毕
    sleep(2);
    return 0;
}

4.2获取线程id
函数原型:

pthred_t pthread_self(void);

功能:获取当前线程的id。
参数:无参。

返回值:返回值为一个无符号长整型。

#define  pthread_t unsigned long int

说明:线程id是在一个进程中的内部标识,但不同进程中的线程id可能相同。、
举例:

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

#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参数。
举例:

#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系统下线程库相关重要的函数具体应用,大家也可以自行举例,验证函数。进一步的去理解线程的真正意义以及如何使用线程相关的开发。

猜你喜欢

转载自blog.csdn.net/asjbfjsb/article/details/79944223
今日推荐