嵌入式学习——(Linux高级编程——线程)

线程

一、pthread 线程概述

pthread(POSIX threads)是一种用于在程序中实现多线程的编程接口。它与进程一样,可以用于实现并发执行任务,但与进程相比有一些不同的特点。

二、优点

1. 比多进程节省资源:进程在创建时需要分配独立的内存空间(0 - 3G),而线程在启动时只需要较小的空间(8M)。此外,线程之间可以共享进程的资源,如内存、文件描述符等,减少了资源的重复分配和占用。

2. 可以共享变量:线程之间可以直接访问共享的内存区域,这使得它们之间的数据共享更加高效和方便。相比之下,进程之间的数据共享需要通过复杂的进程间通信机制。

三、概念和特征

1. 概念:线程被称为轻量级进程,通常是一个进程中的多个任务。进程是系统中最小的资源分配单位,而线程是系统中最小的执行单位。一个进程可以包含多个线程,默认情况下每个进程至少有一个主线程。

2. 特征:
◦ 共享资源:线程可以共享进程的内存空间和其他资源,这使得线程之间的数据交换更加高效。
◦ 效率高:相比多进程,线程的创建和切换开销较小,可以提高程序的执行效率,通常可以提高约 30% 的效率。
◦ 三方库支持:pthread 提供了一套跨平台的线程编程接口,包括clone等系统调用和 POSIX 标准支持,便于移植。在编写代码时需要包含头文件pthread.h,编译时需要加载-lpthread库,线程函数的实现通常在libpthread.so库中。例如,可以使用gcc 1.c -lpthread命令编译包含线程的程序。

四、缺点

1. 稳定性稍差:与进程相比,线程的稳定性稍微差一些。由于线程共享进程的地址空间,如果一个线程出现错误,可能会影响到其他线程甚至整个进程的稳定性。

2. 调试相对麻烦:使用 GDB 调试多线程程序相对复杂,GDB 只能跟踪其中一条线程分支。可以使用info thread命令查看线程信息,然后使用thread命令切换到特定的线程进行调试。
在实际编程中,需要根据任务的特点和需求来选择使用进程还是线程。如果任务复杂,需要独立的资源和更高的稳定性,可以选择使用进程;如果任务相对简单,需要高效的数据共享和并发执行,可以选择使用线程。

线程与进程区别:

一、资源方面

1. 线程:
◦ 相比进程,线程多了共享资源的特性。线程可以共享所属进程的大部分资源,如内存空间、文件描述符等,这使得线程之间的数据交换和通信更加高效,类似于进程间通信(IPC)但更加便捷。
◦ 同时,线程又具有部分私有资源,其中最主要的是私有栈区。每个线程都有自己独立的栈空间,用于存储函数调用的栈帧、局部变量等,保证了线程在执行过程中的独立性。
2. 进程:
◦ 进程间只有私有资源,没有共享资源。每个进程都有独立的内存空间、文件描述符表、打开的文件列表等资源,进程之间的资源相互隔离,不能直接访问对方的资源。

二、空间方面

1. 进程:
◦ 进程空间独立,不同的进程拥有各自独立的地址空间。这意味着一个进程不能直接访问另一个进程的内存空间,它们之间的通信需要通过特定的机制,如管道、消息队列、共享内存等方式进行。
2. 线程:
◦ 线程可以共享空间,因为它们都在所属进程的地址空间内运行。线程之间可以直接通过共享的内存进行通信,无需像进程间通信那样使用复杂的机制。这种直接通信的方式使得线程之间的数据交换更加快速和高效,但也需要注意同步和互斥问题,以避免数据竞争和不一致性。

线程设计框架概述

POSIX 线程提供了一套用于多线程编程的标准接口。其设计框架主要包括创建多线程、线程空间操作以及线程资源回收等步骤。

创建多线程(pthread_create函数)

1.

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

这个函数用于创建一个新的线程。
◦ thread:是一个输出参数,用于存储新创建线程的 ID。在调用之前需要先定义并传入一个pthread_t类型的变量,函数执行成功后,该变量将被赋予新线程的 ID。
◦ attr:用于指定线程的属性,一般设置为NULL表示使用默认属性。
◦ start_routine:是一个函数指针,指向新线程要执行的函数。这个函数接收一个void*类型的参数,并返回一个void*类型的值。通常被称为回调函数,它定义了线程的执行空间。
◦ arg:是传递给回调函数的参数。可以通过这个参数向新线程传递数据。

2. 注意事项:
◦ 一次pthread_create执行只能创建一个线程。
◦ 每个进程至少有一个线程称为主线程。如果主线程退出,那么所有创建的子线程也会退出。
◦ 主线程必须有子线程同时运行才算多线程程序。
◦ 线程 ID 是线程的唯一标识,由 CPU 维护的一组数字。可以使用pstree命令查看系统中多线程的对应关系。多个子线程可以执行同一回调函数。
◦ ps -eLf命令可以查看线程相关信息(Low Weight Process)。ps -eLo pid,ppid,lwp,stat,comm也可以展示一些线程相关的信息。

获取当前线程 ID(pthread_self函数)

1.

pthread_t pthread_self(void);

这个函数用于获取当前线程的线程 ID。
◦ 功能:获取当前正在执行的线程的 ID。
◦ 参数:无。
◦ 返回值:成功时返回当前线程的 ID,类型为pthread_t(通常是一个无符号长整型,可使用%lu格式输出)。失败时返回 -1。
◦ 另一种获取线程 ID 的方式是通过系统调用syscall(SYS_gettid)。 

创建多线程、打印线程id、验证变量共享:

这段代码创建了两个线程和一个主线程,通过共享全局变量a展示了线程之间的数据共享和执行顺序的不确定性。线程 1 先执行并改变了全局变量a的值,线程 2 在延迟 1 秒后输出改变后的a的值,主线程在创建两个子线程后进入无限循环以保持程序运行。 

线程的退出

一、自行退出(自杀)

 void pthread_exit(void *retval);

这个函数用于子线程自行退出。
◦ 功能:当子线程调用这个函数时,子线程会立即退出。它允许子线程在完成任务后或者出现错误时主动退出执行。
◦ 参数:retval是线程退出时候的返回状态,类似于 “临死遗言”。其他线程可以通过特定的方式获取这个返回值,以了解子线程退出的原因或状态。例如,可以在另一个线程中使用pthread_join函数来获取子线程的退出状态。
◦ 返回值:无。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *th(void* arg)
{
    // 输出子线程的线程 ID
    printf("th tid:%lu\n", pthread_self());

    // 子线程通过调用 pthread_exit(NULL) 退出,等效于 return NULL;
    // 这表示子线程正常结束,没有特定的返回值
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    // 输出主线程的线程 ID
    printf("main tid:%lu\n", pthread_self());
    pthread_t tid;
    // 创建子线程
    pthread_create(&tid, NULL, th, NULL);

    while (1)
        sleep(1);
    // 主线程进入无限循环,防止主线程过早退出,否则子线程也会跟着退出

    return 0;
}

二、强制退出(他杀)

int pthread_cancel(pthread_t thread);

这个函数用于主线程强制结束子线程。
◦ 功能:主线程可以调用这个函数来请求结束一个特定的子线程。这类似于一种 “他杀” 行为,主线程主动干预子线程的执行,要求其终止。
◦ 参数:thread是要请求结束的子线程的线程 ID(tid)。
◦ 返回值:成功时返回 0;失败时返回 -1。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

// 线程函数 1,不断输出“接受控制”
void *th1(void* arg)
{
    while (1)
    {
        printf("接受控制\n");
        sleep(1);
    }
    return NULL;
}

// 线程函数 2,不断输出“发送视频”
void *th2(void* arg)
{
    while (1)
    {
        printf("发送视频\n");
        sleep(1);
    }
    return NULL;
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    // 创建第一个线程,执行 th1 函数
    pthread_create(&tid1, NULL, th1, NULL);
    // 创建第二个线程,执行 th2 函数
    pthread_create(&tid2, NULL, th2, NULL);

    int i = 0;
    while (1)
    {
        // 每循环一次,i 加 1
        if (i == 3)
            // 当 i 等于 3 时,强制取消 tid1 对应的线程(即第一个线程)
            pthread_cancel(tid1);

        if (i == 5)
            // 当 i 等于 5 时,强制取消 tid2 对应的线程(即第二个线程)
            pthread_cancel(tid2);

        sleep(1);
        i++;
    }
    return 0;
}

线程的回收

一、线程回收机制

1. 与进程不同,线程没有孤儿线程和僵尸线程的概念。在进程中,如果父进程先于子进程结束,子进程可能成为孤儿进程,由系统的 init 进程收养;如果子进程结束但父进程未及时回收其资源,子进程会成为僵尸进程。而在线程中,主线程结束时,任意生成的子线程都会结束;同时,子线程的结束不会影响主线程的运行。

二、pthread_join函数

int pthread_join(pthread_t thread, void **retval);

这个函数用于回收指定线程的资源。
◦ 功能:它可以将指定的线程资源进行回收,并且具有阻塞等待功能。如果指定的线程没有结束,回收线程的调用者(通常是主线程或其他线程)会被阻塞,直到目标线程结束。这样可以确保线程资源被正确回收,避免资源泄漏。
◦ 参数:
◦ thread是要回收的子线程的线程 ID(tid)。
◦ retval是一个二级指针,用于接收要回收的子线程的返回值或状态。通过传递二级指针,可以在函数内部修改指针所指向的内容,从而获取子线程的返回状态。例如,如果子线程通过pthread_exit(值)退出,可以在回收线程中通过retval获取这个退出值。
◦ 返回值:成功时返回 0;失败时返回 -1。

三、子线程的回收策略

1. 如果预估子线程可以在有限范围内结束,则正常使用pthread_join等待回收。这种情况下,回收线程可以确定子线程会在可预期的时间内结束,因此可以通过阻塞等待的方式确保资源被正确回收。
2. 如果预估子线程可能休眠或者阻塞,则可以等待一定时间后强制回收。例如,可以使用pthread_timedjoin_np函数在指定的时间内等待子线程结束,如果超时则强制回收资源。这种策略适用于子线程可能进入长时间休眠或阻塞状态,而回收线程不能无限期等待的情况。
3. 如果子线程已知必须长时间运行,则不再回收其资源。在某些情况下,子线程可能需要一直运行,例如作为守护线程提供持续的服务。这种情况下,可以不回收子线程的资源,让其在后台持续运行。但需要注意,不回收资源可能会导致资源泄漏,因此需要谨慎使用这种策略,并确保子线程在不再需要时能够正确退出。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void *th(void* arg)
{
    // 在子线程中动态分配 20 个字节的内存空间
    char *p = (char*)malloc(20);
    // 将字符串"hello"复制到动态分配的内存空间中
    strcpy(p, "hello");
    // 子线程休眠 3 秒,模拟子线程执行一些耗时操作
    sleep(3);
    // 返回动态分配的内存地址,作为子线程的返回值
    return p;
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    // 创建一个子线程
    pthread_create(&tid, NULL, th, NULL);

    void *ret = NULL;
    // 等待子线程 tid 结束,并获取子线程的返回值,存储在 ret 中
    pthread_join(tid, &ret);

    // 输出子线程返回的字符串内容
    printf("ret = %s\n", (char*)ret);
    // 注意,这里需要强制转换 ret 为 char* 类型才能正确输出字符串

    // 释放子线程动态分配的内存空间
    free(ret);

    return 0;
}

这段代码创建了一个子线程,子线程在堆上动态分配内存并存储一个字符串,然后主线程等待子线程结束并获取其返回值(即动态分配的内存地址),最后输出子线程的字符串内容并释放动态分配的内存空间。通过这种方式,主线程可以获取子线程的执行结果并正确管理资源。

线程的参数、返回值

一、传参数

1. 传整数:
◦ 普通函数传参示例:int add(int a, int b);中a和b是形参,在调用时如add(x, y);中x和y是实参。
◦ 线程传参:pthread_create(&tid, NULL, fun, x);中最后一个参数可以用来向线程函数传递一个整数参数。在线程函数void * fun(void * arg);中,通过将arg强制转换为合适的类型来获取传入的参数。
2. 传字符串:
◦ 栈区字符数组:在函数内部定义的字符数组,如char buf[] = "";,这种方式定义的字符数组在函数执行完毕后其内存空间可能会被回收,不适合在线程间传递。
◦ 字符串常量:char *p = "hello";,字符串常量存储在静态存储区,其地址在程序运行期间一直有效,但由于其内容不可修改,也不太适合作为线程间传递的参数。
◦ 堆区字符串:char *pc = (char *)malloc(128);,通过动态分配内存的方式创建的字符串,可以作为参数传递给线程函数。在主线程中创建子线程时使用pthread_create(&tid, NULL, fun, pc);,在子线程函数fun(void *arg)中,将arg强制转换为char*类型,即可访问传入的字符串。在主线程中需要在合适的时候使用free(pc);释放动态分配的内存。
3. 传结构体:
◦ 定义结构体类型,例如typedef struct { int a; char b[20]; } MyStruct;。
◦ 用结构体定义变量,如MyStruct myStruct;。
◦ 向pthread_create传结构体变量,pthread_create(&tid, NULL, fun, &myStruct);。
◦ 从子线程中获取结构体数据,在子线程函数fun(void *arg)中,将arg强制转换为结构体指针类型,如MyStruct *myStructPtr = (MyStruct *)arg;,然后就可以访问结构体中的成员。
二、返回值
1. pthread_exit(0)可以改为pthread_exit(9);等,这里传递的参数是一个void*类型的指针,可以指向任何数据类型。例如,pthread_exit可以返回一个整数的地址,如int * p = malloc(4); *p = -10; pthread_exit(p);。
2. pthread_join(tid, NULL);可以改为pthread_join(tid,?);,其中第二个参数是一个二级指针,用于接收子线程的返回值。例如,void **retval; pthread_join(tid, retval);。
子线程退出时可以返回一个内存地址,这个地址所在的内存中可以存储任何数据。但是要注意地址的有效性:
• 栈区变量:错误,子线程结束后栈区变量的地址失效,不能作为返回值。
• 全局变量:失去意义,因为主线程可以直接访问全局变量,不需要通过子线程返回。
• 静态变量和堆区变量:可以作为子线程的返回值,因为它们的内存空间在子线程结束后仍然有效。
主线程通过一个地址形式的变量来接受子线程返回的地址变量,就可以将该地址中的数据取到。

设置线程分离属性

int pthread_detach(pthread_t thread);

这个函数用于设置指定线程的分离属性。
1. 功能:设置指定线程为分离状态。当一个线程被设置为分离状态后,一旦该线程结束执行,它的资源会被自动回收,而不需要其他线程通过调用pthread_join来回收资源。这对于那些不需要被其他线程等待或管理的线程非常有用,可以避免资源泄漏和不必要的等待。
2. 参数:thread是要设置分离属性的线程 ID。通常是在创建线程后,将新创建的线程 ID 传入这个函数来设置分离属性。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* th(void* arg)
{
    // 将当前线程设置为分离状态,这样当线程结束时,系统会自动回收资源
    pthread_detach(pthread_self());
    // 输出当前线程的 ID
    printf("tid : %lu\n", pthread_self());

    return NULL;
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int i;
    for (i = 0; i < 55000; i++)
    {
        // 创建新线程
        int ret = pthread_create(&tid, NULL, th, NULL);
        if (ret!= 0)
        {
            // 如果创建线程失败,跳出循环
            break;
        }
    }
    // 输出成功创建的线程数量
    printf("i = %d\n", i);

    return 0;
}

在这段代码中,main函数创建了多个线程,每个线程在执行时都会将自身设置为分离状态。这样,当这些线程结束执行时,系统会自动回收它们的资源,无需主线程通过pthread_join来回收。循环的目的是创建尽可能多的线程,直到创建线程失败为止,然后输出成功创建的线程数量。这种方式可以测试系统在一定资源条件下能够创建的线程数量上限。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/871541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PDPS软件 那智机器人 (丰田版)离线程序导出处理

在PDPS仿真软件中导出的那智机器人离线程序&#xff0c;一般是无法直接给TFD控制装置-那智机器人&#xff08;丰田式样版&#xff09;导入及识别使用。因此要对导出的程序进行转换编译处理&#xff0c;才能给TFD那智机器人&#xff08;丰田式样版&#xff09;导入离线程序。以下…

HarmonyOS 开发

环境 下载IDE 代码 import { hilog } from kit.PerformanceAnalysisKit; import testNapi from libentry.so; import { router } from kit.ArkUI; import { common, Want } from kit.AbilityKit;Entry Component struct Index {State message: string Hello HarmonyOS!;p…

类与对象(中(2))

开头 大家好啊&#xff0c;上一期内容我们介绍了类与对象中六大默认成员函数中的两种--->构造函数与析构函数&#xff0c;相信大家多少都形成了自己的独到见解。那么今天&#xff0c;我将继续就拷贝构造函数与运算符重载函数来展开讲解&#xff0c;话不多说&#xff0c;我们…

Python版《超级玛丽+源码》-Python制作超级玛丽游戏

小时候最喜欢玩的小游戏就是超级玛丽了&#xff0c;有刺激有又技巧&#xff0c;通关真的很难&#xff0c;救下小公主还被抓走了&#xff0c;唉&#xff0c;心累&#xff0c;最后还是硬着头皮继续闯&#xff0c;终于要通关了&#xff0c;之后再玩还是没有那么容易&#xff0c;哈…

十五年以来 — 战略性云平台服务的演进路径之全面呈现(含亚马逊、微软和谷歌)

Gartner每年都发布对全球IaaS平台进行评估的魔力象限报告。2023年底&#xff0c;Gartner将此项评估的名称改为“战略性云平台服务”&#xff08;Strategic cloud platform services&#xff09;&#xff0c;尽管其核心仍为IaaS&#xff0c;但是&#xff0c;毫无疑问&#xff0c…

手机云电脑游戏测评:ToDesk、易腾云、达龙云、青椒云四款对比分析

文章目录 &#x1f4d1; 引言一、背景概述测试目标 二、测试方案与评测标准2.1 测试设备2.2 评测标准 三、云电脑移动端实测3.1 ToDesk云电脑3.1.1 安装步骤与用户界面3.1.2 性能测试3.1.3 多场景适用性与兼容性3.1.4 性价比 3.2 易腾云电脑3.2.1 安装流程与用户界面3.2.2 帧率…

WebRTC为何成为视频开发领域的首选技术? EasyCVR视频转码助力无缝视频通信

随着互联网的飞速发展&#xff0c;视频通信已成为日常生活和工作中不可或缺的一部分。从在线教育、视频会议到远程医疗、在线直播&#xff0c;视频开发的需求日益增长。在这些应用场景中&#xff0c;选择何种技术来构建视频系统至关重要。 目前&#xff0c;在很多视频业务的开…

Golang | Leetcode Golang题解之第352题将数据流变为多个不相交区间

题目&#xff1a; 题解&#xff1a; type SummaryRanges struct {*redblacktree.Tree }func Constructor() SummaryRanges {return SummaryRanges{redblacktree.NewWithIntComparator()} }func (ranges *SummaryRanges) AddNum(val int) {// 找到 l0 最大的且满足 l0 < val…

Browserless 网页抓取:Playwright 中的 NodeJS

什么是 Playwright&#xff1f; Playwright 是一个用于 Web 测试和自动化的开源框架。基于 Node.js&#xff0c;由 Microsoft 开发&#xff0c;它通过单一 API 支持 Chromium、Firefox 和 WebKit。它可以在 Windows、Linux 和 macOS 上运行&#xff0c;并且兼容 TypeScript、J…

设计模式六大原则(一)--单一职责原则

摘要 单一职责原则是设计模式六大原则之一&#xff0c;强调一个类应该仅有一个引起它变化的原因&#xff0c;即每个类应仅负责一项职责。本文通过详细探讨单一职责原则的定义、实现方式、优缺点及其适用场景&#xff0c;揭示了其在软件设计中的核心地位。通过类的拆分、接口设…

TCP协议段中的六个标志位

目录 ACK SYN RST FIN PSH URG TCP报文格式中的六个标志位由6个比特构成&#xff0c;在通信双方基于TCP协议互相发送报文数据时可以通过报头中标志位来区别对方发送的报文数据的请示。 ACK 确认号是否有效。 接收端对所收到的报文进行检查&#xff0c;若未发现错误&…

周易测算系统开发:融合古典智慧与现代技术的创新实践

一、引言 周易&#xff0c;作为中国古代文化的瑰宝&#xff0c;蕴含着深邃的哲学思想与预测智慧&#xff0c;其独特的六十四卦体系及爻变原理&#xff0c;自古以来便被人们用于探索自然规律、人生哲理及未来趋势。随着科技的飞速发展&#xff0c;将周易智慧与现代计算机技术相结…

StackStorm自动化平台

1. StackStorm概述 1.1 StackStorm介绍 StackStorm是一个开源的事件驱动自动化平台&#xff0c;它允许开发者和系统管理员自动化IT和网络操作。StackStorm结合了IT运维、DevOps和网络安全团队的需求&#xff0c;提供了一个集中式的工作流自动化解决方案&#xff0c;包括事件响…

图像数据处理13

三、空域滤波 3.1滤波器的基本概念 什么是滤波&#xff1f; 简单来说就是从干扰信号中提取出有用的信号 3.1.1空域滤波&#xff08;Spatial Domain Filtering&#xff09; 空域滤波适用于简单的滤波任务&#xff0c;直接对图像的像素空间进行操作。它通过对图像中的每个像…

ES 支持乐观锁吗?如何实现的?

本篇主要介绍一下Elasticsearch的并发控制和乐观锁的实现原理&#xff0c;列举常见的电商场景&#xff0c;关系型数据库的并发控制、ES的并发控制实践。 并发场景 不论是关系型数据库的应用&#xff0c;还是使用Elasticsearch做搜索加速的场景&#xff0c;只要有数据更新&…

ES6-ES13学习笔记

目录 初识ES6 变量声明 解构赋值 对象解构 ​编辑 数组解构 ​编辑模版字符串 字符串扩展 includes() repeat() startsWith() endsWith() 数值扩展 二进制和八进制表示法 &#xff08;Number.&#xff09;isFinite()与isNaN() Number.isInteger() Math.trunc …

AWS不同类型的EC2实例分别适合哪些场景?

Amazon Web Services&#xff08;AWS&#xff09;的弹性计算云&#xff08;EC2&#xff09;提供了多种实例类型&#xff0c;以满足不同的应用需求和工作负载。了解不同类型的 EC2 实例及其适用场景&#xff0c;可以帮助用户更好地优化性能和控制成本。九河云和大家一起了解一下…

元数据管理gravitino学习

元数据管理的组成有几个部分&#xff1a;Metaservice(Gravitino)、Luoshu&#xff08;amoro)、Hive Metastore&#xff0c;其中gravitino是数据管理模块实现元数据统一管理的核心。前面有提到hive metastore可以存储hive的库表元数据信息&#xff0c;可以用于存储关于hive表、列…

一文读懂推荐系统

随着互联网的飞速发展&#xff0c;信息过载已经成为了一个普遍的问题。我们每天都要面对大量的内容&#xff0c;却很难找到真正符合自己兴趣和需求的信息。这时&#xff0c;推荐系统应运而生&#xff0c;它能够根据用户的兴趣和行为&#xff0c;智能地推荐相关内容&#xff0c;…