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

2022-02-24 15:34:50 浏览数 (2)

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

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

0 人点赞