linux环境下线程的介绍和POSIX线程接口应用实例

发布于:2024-03-11 ⋅ 阅读:(72) ⋅ 点赞:(0)

目录

概述

1 线程概念

1.1 线程的特性

1.2 线程的运行状态

2 线程API

2.1 pthread的数据类型

2.2 pthread函数的返回值

2.3 POSIX线程接口

2.3.1 创建线程函数pthread_create

2.3.2 终止线程

2.3.3 线程ID

2.3.4 连接已终止线程

2.3.5 线程分离

3 线程VS进程

4 线程使用的案例

4.1 使用pthread_exit退出线程

4.1.1 功能介绍

4.1.2 编写代码

4.1.3 验证

4.2 修改线程属性

4.2.1 功能介绍

4.2.2 编写代码

4.2.3 验证

5 参考文献


概述

本文详细介绍线程的相关知识,并介绍和线程相关的pthread的API,对其中重要的函数做了详细的说明,还比较了线程和进程的优缺点。最后,使用pthread API编写了两个案例,以更好的认识和理解这些接口函数的使用方法。

1 线程概念

每一个进程有一个地址空间和一个控制线程。同一个地址空间准并行(对于单核CPU来说,本质上还是串行执行的)运行多个控制线程,这些线程就像分离的进程。所以,线程也可以被称作mini进程。下图可形象的表示进程和线程的关系:

1.1 线程的特性

1)线程依附于进程存在,当该进程消亡后,运行在该进程下的线程也全部消亡;

2)同一个进程下的所有线程,共享同一个地址空间和所有可用数据;

3)线程是轻量级别的运行程序(和进程比较),它们比进程更容易创建和销毁;

4)在存在大量计算和大量IO处理的系统中,多线程允许这些活动重叠进行,从而加快应用程序的执行速度;

5)进程用于把资源集中在一起,线程则是在CPU上被调度执行的实体;

6)同一个进程中,允许运行多个线程。

1.2 线程的运行状态

一个线程可以处在若干中状态中的任何一个:运行态,阻塞态,就绪态或者终止态

运行态:正在运行的线程,拥有CPU的资源。并且活跃执行。

阻塞态:线程被阻塞,以便等待其他事件或者资源来释放它的

就绪态:线程可以被调度,只要轮到它就马上被执行

终止态:线程停止运行,并且不再被唤醒

2 线程API

2.1 pthread的数据类型

数据类型 描述
pthread_t 线程ID
pthread_mutex_t 互斥对象
pthread_mutexattr_t 互斥属性对象
pthread_cond_t 条件变量
pthread_condattr_t 条件变量的属性对象
pthread__key_t 线程特有数据的键
pthread_once_t 一次初始化控制上下文
pthread_attr_t 线程的属性对象

2.2 pthread函数的返回值

pthread函数返回0,表示成功,返回一个正值表示失败。这只是为了与使用 errno 到的函数进行兼容,在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局变量,这样可以把错误的范围限制在引起出错的函数中 。

2.3 POSIX线程接口

为了实现可移植的线程程序,IEEE标准定义了线程的标准。这里介绍一些使用线程常用到的一些接口函数

函数名 描述
pthread_create 创建一个新线程
pthread_exit 结束调用的线程
pthread_join 等待一个特定的线程退出
pthread_yield 释放CPU来运行另外一个线程
pthread_attr_init 创建并初始化一个线程的属性结构
pthread_attr_destroy 删除一个线程的属性结构

2.3.1 创建线程函数pthread_create

主线程可以使用库函数 pthread_create()负责创建一个新的线程, 创建出来的新线程被称为主线程的子线 程,函数声明如下:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

函数参数介绍

参数 描述
thread pthread_t 类型指针, 当 pthread_create()成功返回时,新创建的线程的线程 ID 会保存在参数 thread所指向的内存中,后续的线程相关函数会使用该标识来引用此线程
attr pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区, pthread_attr_t 数据类型定义了线程的各种属性,如果将参数 attr 设置为 NULL, 那么表示将线程的所有属性设置为默认值,以此创建新线程。
start_routine 参数 start_routine 是一个函数指针,指向一个函数, 新创建的线程从 start_routine()函数开始运行,该函数返回值类型为void *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create()函数的第四个参数 arg。如果需要向 start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为 arg 参数传入。
arg 传递给 start_routine()函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。 当然也可将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。

2.3.2 终止线程

可以通过如下方式终止线程的运行:

1)线程的 start 函数执行 return 语句并返回指定值,返回值就是线程的退出码;

2)线程调用 pthread_exit()函数;

3)调用 pthread_cancel()取消线程;

4)如果进程中的任意线程调用 exit(),那么将会导致所有的进程立即终止

#include <pthread.h>
void pthread_exit(void *retval);

2.3.3 线程ID

进程内部的每一个线程都有唯一的便是,称为线程ID。 线程 ID 使用 pthread_t 数据类型来表示,一个线程可通过库函数 pthread_self()来获取自己的线程 ID,其函数声明如下所示:

#include <pthread.h>
pthread_t pthread_self(void);

线程 ID 在应用程序中非常有用,原因如下:

1) 不同的线程函数,利用线程 ID 来标识要操作的目标线程, 这些函数包括pthread_cancel()、 pthread_detach()、 pthread_join()、 pthread_kill()

2) 在一些应用程序中,以特定线程的线程 ID 作为动态数据结构的标签,这某些应用场合颇为有用,既可以用来标识整个数据结构的创建者或属主线程,又可以确定随后对该数据结构执行操作的具体线程。

2.3.4 连接已终止线程

函数pthread_join() 等待由pthread_t标识的线程终止,如果该线程已经终止,则其立即返回。 通过参数 thread(线程 ID) 指定需要等待的线程;

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

参数介绍

参数 描述
thread 通过参数 thread(线程 ID) 指定需要等待的线程
retval 如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态(即目标线程通过 pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)复制到retval 所指向的内存区域;如果目标线程被 pthread_cancel()取消, 则将 PTHREAD_CANCELED 放在retval 中。 如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL。

pthread_join()终止线程的特点:

1)调用 pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则 pthread_join() 立刻返回。 如果多个线程同时尝试调用 pthread_join()等待指定线程的终止,那么结果将是不确定的。

2) 若线程并未分离,则必须使用 pthread_join()来等待线程终止,回收线程资源;如果线程终止后,其它线程没有调用 pthread_join()函数来回收该线程,那么该线程将变成僵尸线程,与僵尸进程的概念相类似;同样,僵尸线程除了浪费系统资源外,若僵尸线程积累过多,那么会导致应用程序无法创建新的线程。

pthread_join()执行的功能类似于针对进程的 waitpid()调用,不过二者之间存在一些显著差别: 1)线程之间关系是对等的。进程中的任意线程均可调用 pthread_join()函数来等待另一个线程的终止。譬如,如果线程 A 创建了线程 B,线程 B 再创建线程 C,那么线程 A 可以调用 pthread_join()等待线程 C 的终止,线程 C 也可以调用 pthread_join()等待线程 A 的终止;这与进程间层次关系不同,父进程如果使用 fork()创建了子进程,那么它也是唯一能够对子进程调用 wait()的进程,线程之间不存在这样的关系。

2)不能以非阻塞的方式调用 pthread_join()。对于进程,调用 waitpid()既可以实现阻塞方式等待、也可以实现非阻塞方式等待。

2.3.5 线程分离

当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用 pthread_detach()将指定线程进行分离,也就是分离线程。其函数声明如下所示:

#include <pthread.h>
int pthread_detach(pthread_t thread);

调用pthread_detach()后:

1)一旦线程处于分离状态,不能在使用pthread_join()来获取状态,也无法返回可连接状态;

2)其他线程调用exit(),或者主线程执行return。即使已经分离的线程也会收到影响,此时不管线程处于那种状态,进程下的所有线程都会终止。

3 线程VS进程

在应用程序中,使用多线程还是多进程呢?这要根据应用的场景进行选择。现在,先来看看多线程的优缺点:

线程的优点

1)线程间共享数据简单

2)创建线程要远快于进程

线程的缺点

1)多线程编程,要确保以安全的方式调用函数,多进程无需考虑这个

2)某个线程的bug可能会影响其他线程,因为它们共享相同的地址空间和其他属性

3)处在同一个进程之下的所有线程都在竞争宿主下有限的地址空间;

4)多线程中处理信号,需要特别小心;

5)在多线程程序中,所有线程运行在同一个程序下。多进程可以运行在不同的程序下。

4 线程使用的案例

4.1 使用pthread_exit退出线程

4.1.1 功能介绍

创建了两个线程,其中第一个线程是在程序运行到中途时调用 pthread_exit函数退出,第二个线程正常运行退出。在主线程中收集这两个线程的退出信息,并释放资源。

4.1.2 编写代码

创建test_thread.c文件,编写如下代码

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     :  test_thread.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : pthread API test
其他       : 无
日志       : 初版V1.0 2024/03/04
​
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
​
/* 线程 -1 */
void thread1(void)
{
    int i = 0;
    
    for( i=0; i < 6; i++){
        printf("This is pthread1.\n");
        if( i == 2 ){
            pthread_exit(0);
        }
        sleep(1);
    }
}
​
/* 线程 -2 */
void thread2(void)
{
    int i = 0;
    
    for( i = 0; i < 3; i++ ){
        printf("This is pthread2.\n");
    }
    pthread_exit(0);
}
​
​
int main(void)
{
    pthread_t id1,id2;
    int ret;
    
    /* 创建线程 -1 */
    ret = pthread_create(&id1,NULL,(void *) thread1,NULL);
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
    
    /* 创建线程 -2 */
    ret = pthread_create(&id2,NULL,(void *) thread2,NULL);
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
    
    /*等待线程结束*/
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    
    exit (0);
}
Makefile 文件:
CFLAGS= -Wall -lpthread -O2
CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip
​
test_thread: test_thread.o
    $(CC) $(CFLAGS) -o test_thread test_thread.o 
    $(STRIP) -s test_thread
​
clean:
    rm -f test_thread test_thread.o

4.1.3 验证

编译代码,然后在板卡上运行,得到运行结果:

4.2 修改线程属性

4.2.1 功能介绍

第一个线程设置为分离属性,并将第二个线程设置为始终运行状态,这样就可以在第二个线程运行过程中查看内存值的变化。

4.2.2 编写代码

创建test_thread.c,然后编写如下代码

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     :  test_thread.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : pthread API test
其他       : 无
日志       : 初版V1.0 2024/03/04
​
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
​
/* 线程 -1 */
void thread1(void)
{
    int i = 0;
    
    for( i=0; i < 6; i++){
        printf("This is pthread1.\n");
        if( i == 2 ){
            pthread_exit(0);
        }
        sleep(1);
    }
}
​
/* 线程 -2 */
void thread2(void)
{
    int i = 0;
    
    for( i = 0; i < 3; i++ ){
        printf("This is pthread2.\n");
    }
    pthread_exit(0);
}
​
​
​
int main(void)
{
    pthread_t id1,id2;
    int ret;
    pthread_attr_t attr;
    
    /*初始化线程*/
    pthread_attr_init(&attr);
    
    /*设置线程绑定属性*/
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    
    /*设置线程分离属性*/
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
    /*创建线程*/
    ret=pthread_create(&id1,&attr,(void *) thread1,NULL);
    
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
    
    ret=pthread_create(&id2,NULL,(void *) thread2,NULL);
    
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
   
    pthread_join(id2,NULL);
    return (0);
}

4.2.3 验证

编译代码,然后在板卡上运行,得到运行结果:

通过比较运行前后的内存,可知在线程1执行完成后,会立即释放内存

5 参考文献

  1. 《现代操作系统》

  2. 《linux/unix系统编程手册》

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到