Linux系统编程【线程】

举报
xcc-2022 发表于 2022/10/24 20:53:30 2022/10/24
【摘要】 6.7 线程的优缺点优点:Ø 提高程序并发性Ø 开销小Ø 数据通信、共享数据方便缺点:Ø 库函数,不稳定Ø 调试、编写困难、gdb不支持Ø 对信号支持不好优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。 07. 线程常用操作 7.1 线程号就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进...

6.7 线程的优缺点

优点:

Ø 提高程序并发性

Ø 开销小

Ø 数据通信、共享数据方便

缺点:

Ø 库函数,不稳定

Ø 调试、编写困难、gdb不支持

Ø 对信号支持不好

优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。

07. 线程常用操作

7.1 线程号

就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。

进程号用 pid_t 数据类型表示,是一个非负整数。线程号则用 pthread_t 数据类型来表示,Linux 使用无符号长整数表示。

有的系统在实现pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。

pthread_self函数:

#include <pthread.h>

pthread_t pthread_self(void);
功能:
    获取线程号。
参数:
    无
返回值:
    调用线程的线程 ID 。

示例代码:

image-20220926090129507

pthread_equal函数:

int pthread_equal(pthread_t t1, pthread_t t2);
功能:
    判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数:
    t1,t2:待判断的线程号。
返回值:
    相等:  非 0
    不相等:0

参考程序:

int main()
{
    pthread_t thread_id;

    thread_id = pthread_self(); // 返回调用线程的线程ID
    printf("Thread ID = %lu \n", thread_id);

    if (0 != pthread_equal(thread_id, pthread_self()))
    {
        printf("Equal!\n");
    }
    else
    {
        printf("Not equal!\n");
    }

    return 0;
}

7.2 线程的创建

pthread_create函数:

#include <pthread.h>

int pthread_create(pthread_t *thread,
            const pthread_attr_t *attr,
            void *(*start_routine)(void *),
            void *arg );
功能:
    创建一个线程。
参数:
    thread:线程标识符地址。
    attr:线程属性结构体地址,通常设置为 NULL。
    start_routine:线程函数的入口地址。
    arg:传给线程函数的参数。
返回值:
    成功:0
    失败:非 0

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。

由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印。

参考程序(无参):

//线程调度之后执行任务
void *fun(void *arg)
{
    printf("新的线程执行任务 tid:%lu\n",pthread_self());
    return NULL;
}
int main()
{
    int ret = -1;
 
    ret = pthread_create(&pt,NULL,fun,NULL);
    if(0 != ret)
    {
        printf("pthread_create failed...\n");
        return 1;
    }
    printf("main thread...tid:%lu\n",pthread_self());
    printf("为了让新线程执行完,主进程睡眠1秒...\n");
    sleep(1);
    return 0;
}
image-20220926192503774

为什么要睡眠?因为创建进程之后再执行一遍子线程还未完成调度,主进程就退出了,也就只会打印主进程信息。还有线程创建之后,谁先执行是由cpu调度的;线程号也是唯一。

参考代码2(传参):

void *fun1(void *arg)
{
    int id = (long)arg;//指针强制转换成long,然后再强转int,不然会出截断警告
    printf("线程 id :%d\n",id);
    return NULL;
}
	int ret  = -1;
    pthread_t pt1 = -1;
    ret = pthread_create(&pt1,NULL,fun1,(void*)0x3);//把(*void)0x3当地址传给fun1

运行结果:线程 id :3

7.3 线程资源回收

pthread_join函数:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
功能:
    等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
    thread:被等待的线程号。
    retval:用来存储线程退出状态的指针的地址。
返回值:
    成功:0
    失败:非 0

参考代码:

void *thead(void *arg)
{
    static int num = 123; //静态变量

    printf("after 2 seceonds, thread will return\n");
    sleep(2);

    return &num;//或者 return (void*)0x3;
}

int main()
{
    pthread_t tid;
    int ret = 0;
    void *value = NULL;

    // 创建线程
    pthread_create(&tid, NULL, thead, NULL);

    // (会阻塞)等待线程号为 tid 的线程,如果此线程结束就回收其资源
    // &value保存线程退出的返回值
    pthread_join(tid, &value);

    printf("value = %d\n", *((int *)value));
    //简写:printf("value =%p\n",value);

    return 0;
}

7.4 线程分离

为了解决pthread_join线程阻塞问题,引申除线程分离;

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

pthread_detach函数:

#include <pthread.h>

int pthread_detach(pthread_t thread);
功能:
    使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
    thread:线程号。
返回值:
    成功:0
    失败:非0

image-20220926212004060

7.5 线程退出

在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。

  • 线程从执行函数中返回。
  • 线程调用pthread_exit退出线程。
  • 线程可以被同一进程中的其它线程取消。

pthread_exit函数:

#include <pthread.h>

void pthread_exit(void *retval);
功能:
    退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
    retval:存储线程退出状态的指针。
返回值:无 

参考程序:

void *thread(void *arg)
{
    static int num = 123; //静态变量
    int i = 0;
    while (1)
    {
        printf("I am runing\n");
        sleep(1);
        i++;
        if (i == 3)
        {
            pthread_exit((void *)&num);
            // return &num;
        }
    }

    return NULL;
}

int main(int argc, char *argv[])
{
    int ret = 0;
    pthread_t tid;
    void *value = NULL;
    
    pthread_create(&tid, NULL, thread, NULL);

    pthread_join(tid, &value);
    printf("value = %d\n", *(int *)value);

    return 0;
}

7.6 线程取消

#include <pthread.h>

int pthread_cancel(pthread_t thread);
功能:
    杀死(取消)线程
参数:
    thread : 目标线程ID。
返回值:
    成功:0
    失败:出错编号

注意:线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。

类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。

杀死线程也不是立刻就能完成,必须要到达取消点。

取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。

可粗略认为一个系统调用(进入内核)即为一个取消点。

参考程序:

void *thread_cancel(void *arg)
{
    int i = 0;
    for(i=0;i<5;i++)
    {
        printf("pthread do working...\n",i);
        sleep(1);
    }
    //等价于return 线程退出
    pthread_exit(NULL);
    //return NULL;
    //终止整个进程
    //exit(0);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread_cancel, NULL); //创建线程

    sleep(3);   
    printf("主线程睡眠3秒,取消了子进程>...\n");
    pthread_cancel(tid); //取消tid线程

    //pthread_join(tid, NULL);无作用

    return 0;
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。