【嵌入式Linux】<总览> 多线程(更新中)

文章目录

前言

一、多线程

1. 概述

2. 创建线程

3. 线程退出

4. 线程回收

5. 线程分离

6. 线程取消

7. 线程的ID比较

二、线程同步


前言

记录学习多线程的知识重点与难点,若涉及版权问题请联系本人删除!


一、多线程

1. 概述

  • 线程是轻量级的进程,一个进程可以涵盖多个线程。
  • 线程是操作系统调度的最小单位,进程是资源分配的最小单位。
  • 多个线程有不同的“地位”:进程的虚拟地址空间的生命周期默认和主线程一样,与创建的子线程无关。当主线程执行完毕,虚拟地址空间就会被释放。
  • 每个线程都有唯一的线程ID(无符号的长整型数),类型为pthread_t。调用pthread_self函数就能得到当前线程的ID。
#include <pthread.h>

pthread_t pthread_self(void);

//使用pthread库,链接时需要加上选项-lpthread来指定动态库

进程与线程的区别:

  • 进程有独立的地址空间,线程共用同一个地址空间。每个线程都有自己的栈区和寄存器,多个线程共享代码段、堆区、全局数据区和文件描述符表。
  • 线程的上下文切换比进程快得多。

上下文切换:进程/线程分时复用CPU时间片,在切换前会保存当前状态,下次执行该任务时加载保存的状态。

控制多个线程个数:

  • 针对文件IO:线程个数=2*CPU核心数。
  • 针对复杂算法:线程个数=CPU核心数。

2. 创建线程

调用pthread_create函数:

【1】头文件:#include <pthread.h>

【2】函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

【3】参数说明:

  • thread:传出的参数,保存创建的线程ID。
  • attr:线程的属性,一般为NULL表示默认的属性。
  • start_routine:函数指针,线程的执行函数。
  • arg:作为实参传递到start_routine指向的函数内部。

【4】返回值:成功返回0,失败返回错误码。

程序实例:创建子线程,并在主线程内打印子线程和主线程的tid。同时,在子线程执行函数中打印信息。

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

void* doing(void *arg)
{
    printf("我是子线程, ID: %ld\n", pthread_self());
    return NULL;
}

int main(int argc, char ** argv)
{
    /* 创建子线程 */
    pthread_t tid;
    if (!pthread_create(&tid, NULL, doing, NULL)) {
        printf("子线程创建成功,其ID为:%ld\n", tid);
    }

    /* 主线程才会执行 */
    printf("主线程的ID:%ld\n", pthread_self());
    while (1) {
        sleep(1);
    }
    return 0;
}

3. 线程退出

调用pthread_exit函数,调用后线程就退出了。只有当所有线程都退出了,虚拟地址空间才会释放。

【1】头文件:#include <pthread.h>

【2】函数原型:void pthread_exit(void *retval);

【3】参数说明:retval表示子线程退出后返回主线程的数据。不需要时使用NULL。

程序实例:创建子线程,在子线程的执行函数中睡眠3秒后打印信息,主进程创建完子线程后调用pthread_exit函数退出。

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

/* 子线程执行函数 */
void* doing(void *arg)
{
        sleep(3);//延迟三秒,确定主线程是否会退出
        for (int i = 0; i < 9; ++i) {
                printf("我是子线程!\n");
                if (i == 3) {
                        pthread_exit(NULL);
                }
        }
        return NULL;
}

int main(int argc, char **argv)
{
        /* 创建子线程 */
        pthread_t tid;
        if (!pthread_create(&tid, NULL, doing, NULL)) {
                printf("子线程创建成功,ID: %ld\n", tid);
        }

        /* 主线程:调用pthread_exit不会释放虚拟地址空间 */
        pthread_exit(NULL);
        return 0;
}

4. 线程回收

主线程调用pthread_join函数来阻塞式回收子线程。若子线程还在运行,那么该函数就会阻塞。该函数只能回收一个子线程,若想回收多个可以考虑采用循环。

【1】头文件:#include <pthread.h>

【2】函数原型:int pthread_join(pthread_t thread, void **retval);

【3】参数说明:

  • thread:要回收的子线程ID。
  • retval:接收子线程通过pthread_exit传出的数据。

【4】返回值:回收成功返回0,失败返回错误号。

【5】注意:

  • 若子线程返回的数据位于子线程的栈区中,那么当子线程退出后其栈区就会被释放,主线程获取的数据就是无效的。
  • 由于多个线程共用堆区和全局数据区,可以将子线程的数据保存于全局变量中。
  • 由于主线程要回收子线程,一般都是最后退出。因此,可以将主线程的栈区变量传入子线程,在子线程中进行修改。

程序实例:借助主线程的栈区变量,将其传入子线程中,子线程将数据保存至该变量中,最后通过pthread_exit函数返回主线程,主线程调用pthread_join函数回收子线程并获取子线程返回的数据。

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

void* doing(void *arg)
{
    int *i = (int *)arg;
    *i = 666666;
    printf("子线程将参数修改为: %d\n", *i);
    pthread_exit(i);
    return NULL;
}

int main(int argc, char **argv)
{
    /* 创建子线程 */
    pthread_t tid;
    int variable = 0;//主线程栈区变量
    pthread_create(&tid, NULL, doing, &variable);

    /* 主线程获取子线程退出时的数据 */
    void *ret = NULL;
    pthread_join(tid, &ret);//回收子线程
    printf("主线程获取子线程数据: %d\n", *(int*)ret);
    return 0;
}

5. 线程分离

如果总是让主线程来回收子线程,那么可能会出现子线程一直运行,而主线程阻塞在pthread_join函数,无法执行其他任务。因此,可以调用pthread_detach函数将子线程分离。当子线程退出时,其内核资源就会被系统其他进程接管并回收。

【1】头文件:#include <pthread.h>

【2】函数原型:int pthread_detach(pthread_t thread);

【3】参数说明:thread是要分离的线程ID。

【4】返回值:成功返回0,失败返回错误号。

程序实例:主线程与子线程分离,主线程执行完任务后就退出。

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

void* doing(void *arg)
{
    for (int i = 0; i < 9; ++i) {
        printf("Hello, Can! %d\n", i);
        if (i == 4) {
            pthread_exit(NULL);
        }
    }
    return NULL;
}

int main(int argc, char **argv)
{
    /* 创建子线程  */
    pthread_t tid;
    int retCreate = pthread_create(&tid, NULL, doing, NULL);
    if (retCreate != 0) {
        perror("pthread_create error!");
        return -1;
    }

    /* 主线程与子线程分离 */
    int retDetach = pthread_detach(tid);
    if (retDetach != 0) {
        perror("pthread_detach error!");
        pthread_join(tid, NULL);
    }

    /* 主线程执行其他任务 */
    for (int i = 0; i < 5; i++) {
        printf("我是主线程, %d\n", i);
    }
    pthread_exit(NULL);

    return 0;
}

6. 线程取消

线程取消就是一个线程杀死另一个线程。线程A杀死线程B需要两个条件:①线程A调用pthread_cancel函数;②线程B进行一次系统调用,从用户态切换回内核态。

【1】头文件:#include <pthread.h>

【2】函数原型:int pthread_cancel(pthread_t thread);

【3】参数说明:thread是要杀死的线程ID。

【4】返回值:成功返回0,失败返回错误号。

程序实例:主线程杀死子线程。当子线程执行pthread_self函数时就会被杀死,因为该函数间接调用了系统调用函数。

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

void* doing(void *arg)
{
    printf("子线程: 我要调用pthread_self了!\n");
    printf("子线程: %ld\n", pthread_self());
    sleep(2);//确保杀死子线程时间足够
    printf("子线程: 我还活着吗?\n");

    return NULL;
}

int main(int argc, char **argv)
{
    /* 创建子线程 */
    pthread_t tid;
    if (pthread_create(&tid, NULL, doing, NULL) != 0) {
        perror("pthread_create error!");
        return -1;
    }

    /* 主线程杀死子线程 */
    if (pthread_cancel(tid) != 0) {
        perror("pthread_cancel error!");
    }

    /* 主线程退出  */
    pthread_exit(NULL);
    return 0;
}

7. 线程的ID比较

调用pthread_equal函数来比较两个线程的ID是否相等。

【1】头文件:#include <pthread.h>

【2】函数原型:int pthread_equal(pthread_t t1, pthread_t t2);

【3】参数说明:t1和t2就是两个线程的ID。

【4】返回值:相同返回非0值,不同返回0.

程序实例:主线程创建子线程,然后判断主线程和子线程的ID是否相同。

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

void* doing(void *arg)
{
    printf("我是子线程\n");
    return NULL;
}

int main(int argc, char **argv)
{
    /* 创建子线程 */
    pthread_t tid;
    if (pthread_create(&tid, NULL, doing, NULL) != 0) {
        perror("create error!");
        return -1;
    }

    /* 比较线程ID是否相等*/
    pthread_t tidMain = pthread_self();
    if (pthread_equal(tidMain, tid) > 0) {
        printf("主线程ID: %ld, 子线程ID: %ld, 二者相等\n", tidMain, tid);
    } else {
        printf("主线程ID: %ld, 子线程ID: %ld, 二者不相等\n", tidMain, tid);
    }

    return 0;
}


二、线程同步

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

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

相关文章

期末考试后班主任如何发布学生成绩?

期末考试成绩一出&#xff0c;家长们便急切地想要了解孩子的学习情况。以往&#xff0c;老师们需要一个个私信家长&#xff0c;将成绩单发送出去&#xff0c;这项工作既繁琐又耗时。期末之际&#xff0c;老师们的工作本就繁重&#xff0c;如何有效减轻他们的负担&#xff0c;让…

构建现代医疗:互联网医院系统源码与电子处方小程序开发教学

本篇文章&#xff0c;笔者将探讨互联网医院系统的源码结构和电子处方小程序的开发&#xff0c;帮助读者更好地理解和掌握这些前沿技术。 一、互联网医院系统源码结构 互联网医院系统通常由多个模块组成&#xff0c;每个模块负责不同的功能。以下是一个典型的互联网医院系统的主…

【云原生】Prometheus 使用详解

目录 一、前言 二、服务监控概述 2.1 什么是微服务监控 2.2 微服务监控指标 2.3 微服务监控工具 三、Prometheus概述 3.1 Prometheus是什么 3.2 Prometheus 特点 3.3 Prometheus 架构图 3.3.1 Prometheus核心组件 3.3.2 Prometheus 工作流程 3.4 Prometheus 应用场景…

Linux 进程信号篇

文章目录 1. 生活中的信号2. 信号的概念3. 信号的产生3.1 系统调用3.2 软件条件3.2 异常3.3 Core和Term的区别 4. 信号的保存5. 信号的处理5.1 地址空间的进一步理解5.2 键盘输入数据的过程5.3 理解OS如何正常运行5.3.1 OS如何运行5.3.2 如何理解系统调用 5.4 内核态和用户态 6…

容器技术-docker4

一、docker资源限制 在使用 docker 运行容器时&#xff0c;一台主机上可能会运行几百个容器&#xff0c;这些容器虽然互相隔离&#xff0c;但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制&#xff0c;那么容器之间会互相影响&#xff0c;小的来说…

容器技术-docker2

容器化技术Docker Docker介绍 官网&#xff1a; docker.io docker.com 公司名称&#xff1a;原名dotCloud 14年改名为docker 容器产品&#xff1a;docker 16年已经被更名为Moby docker-hub docker.io docker容器历史 和虚拟机一样&#xff0c;容器技术也是一种资源隔…

为什么前端传了token,后端一直获取不到?一直报跨域错误?

这是我的前端代码 这是我的后端拦截器 那就需要了解一下 预检请求 对于非简单请求&#xff08;如PUT、DELETE或包含自定义HTTP头的请求&#xff09;&#xff0c;浏览器会先发送一个OPTIONS请求到目标服务器&#xff0c;询问是否允许该跨域请求。这个过程称为预检请求。 当opt…

(超详细)数据结构——“栈”的深度解析

前言&#xff1a; 在前几章我们介绍了线性表的基本概念&#xff0c;也讲解了包括顺序表&#xff0c;单链表&#xff0c;双向链表等线性表&#xff0c;相信大家已经对线性表比较熟悉了&#xff0c;今天我们要实现线性表的另一种结构——栈。 1.栈的概念 栈&#xff1a;一种特殊…

熊猫烧香是什么?

熊猫烧香&#xff08;Worm.WhBoy.cw&#xff09;是一种由李俊制作的电脑病毒&#xff0c;于2006年底至2007年初在互联网上大规模爆发。这个病毒因其感染后的系统可执行文件图标会变成熊猫举着三根香的模样而得名。熊猫烧香病毒具有自动传播、自动感染硬盘的能力&#xff0c;以及…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-39实战Kaggle比赛:狗的品种识别(ImageNet Dogs)

39实战Kaggle比赛&#xff1a;狗的品种识别&#xff08;ImageNet Dogs&#xff09; 比赛链接&#xff1a;Dog Breed Identification | Kaggle 1.导入包 import torch from torch import nn import collections import math import os import shutil import torchvision from…

x-file-storage一行代码进行文件上传,摆脱阿里云,腾讯云,华为云等不同云的学习,简单高效

问题&#xff1a; 不使用x-file-storage时如果使用某个云首先需要学习他的sdk,这样很麻烦&#xff0c;而x-file-storage集成了各种云的上传&#xff0c;只需要进行配置即可一行代码进行上传 使用 官方地址&#xff1a;X File Storage 一行代码将文件存储到本地、FTP、SFTP、…

【小沐学AI】Python实现语音识别(whisperX)

文章目录 1、简介1.1 whisper1.2 whisperX 2、安装2.1 安装cuda2.2 安装whisperX 结语 1、简介 1.1 whisper https://arxiv.org/pdf/2212.04356 https://github.com/openai/whisper Whisper 是一种通用语音识别模型。它是在各种音频的大型数据集上训练的&#xff0c;也是一个…

时间复杂度计算

要求算法的时间复杂度时&#xff0c;我们可以分析给定表达式 的阶。让我们来逐步分析&#xff1a; 分析阶的定义&#xff1a; 当我们说一个表达式的时间复杂度是 ( O(g(n)) )&#xff0c;我们指的是当 ( n ) 趋近无穷大时&#xff0c;表达式的增长率与 ( g(n) ) 的增长率相似。…

两数之和你会,三数之和你也会吗?o_O

前言 多少人梦想开始的地方&#xff0c;两数之和。 但是今天要聊的不是入门第一题&#xff0c;也没有面试官会考这一题吧…不会真有吧&#xff1f; 咳咳不管有没有&#xff0c;今天的猪脚是它的兄弟&#xff0c;三数之和&#xff0c;作为双指针经典题目之一&#xff0c;也是常…

SolidWorks强大的工程设计软件下载安装,实现更为高效的设计流程

结构分析&#xff1a;SolidWorks作为一款强大的工程设计软件&#xff0c;集成了有限元分析&#xff08;FEA&#xff09;工具&#xff0c;这一工具的运用在工程设计领域具有举足轻重的地位。FEA工具在SolidWorks中的集成&#xff0c;使得工程师们能够便捷地对零件和装配体进行精…

第三十八篇——复盘:如何把信息论学以致用?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 信息论是一个好的学科&#xff0c;里面穿插的知识符合这个时代我们应该具…

STM32F1+HAL库+FreeTOTS学习2——STM32移植FreeRTOS

STM32F1HAL库FreeTOTS学习2——STM32移植FreeRTOS 获取FreeRTOS源码创建工程窥探源码移植 上期我们认识了FreeRTOS&#xff0c;对FreeRTOS有了个初步的认识&#xff0c;这一期我们来上手移植FreeRTOS到STM32上。 获取FreeRTOS源码 进入官网&#xff1a;https://www.freertos.o…

vue+go实现web端连接Linux终端

vuego实现web端连接Linux终端 实现效果 实现逻辑1——vue 依赖包 "xterm": "^5.3.0","xterm-addon-attach": "^0.9.0","xterm-addon-fit": "^0.8.0"样式和代码逻辑 <template><a-modalv-model:visib…

短视频矩阵系统:打造品牌影响力的新方式

一、短视频矩阵概念 短视频营销革命&#xff1a;一站式解决策略&#xff01;短视频矩阵系统是一款专为企业营销设计的高效工具&#xff0c;旨在通过整合和优化众多短视频平台资源&#xff0c;为企业呈现一个全面的短视频营销策略。该系统致力于协助企业以迅速且高效的方式制作…

【ARM】MCU和SOC的区别

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 了解SOC芯片和MCU芯片的区别 2、 问题场景 用于了解SOC芯片和MCU芯片的区别&#xff0c;内部结构上的区别。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;无 2&#xff09;、电脑环境&#xff1a;无 3&am…