【Linux】Linux线程

一、Linux线程的概念

1.什么是线程

1.一个进程的一个执行线路叫做线程,线程的一个进程内部的控制序列。

2.一个进程至少有一个执行线程

3.线程在进程内部,本质是在进程地址空间内运行

4.操作系统将进程虚拟地址空间的资源分配给每个执行流,就成了线程执行流。

2.线程的优点

1.创建新线程的成本远低于创建新进程。

2.与进程切换相比,线程切换需要较少的操作系统资源。

3.线程比进程占用更少的资源。

4.它们能有效地利用多处理器并行处理的能力。

5.在等待缓慢的I/O操作时,程序还可以进行其他计算任务。

6.在多处理器系统中,计算密集型应用通过分解计算任务到多个线程中来运行。

7.I/O密集型应用通过重叠I/O操作来提升性能,允许线程同时等待多个I/O操作。

3.线程的缺点

a.增加额外开销

计算密集型线程,若很少受外部事件阻塞,通常不能与其他线程共享同一处理器。当计算密集型线程的数量超过可用处理器时,可能会导致显著的性能损失。这种性能损失主要是由于增加了额外的同步和调度开销,而可用资源保持不变。

b.线程安全

多线程编程需要更全面和深入的考量。在多线程程序中,由于时间分配上的微小偏差或共享了不应共享的变量,可能会产生不良影响,这意味着线程之间缺乏必要的保护。

c.缺乏访问控制

进程是访问控制的基本单位。在一个线程中调用某些操作系统函数可能会影响整个进程。

d.编程难度提高

编写和调试多线程程序比单线程程序更加困难。

e.线程异常

如果单个线程出现除零错误或野指针问题导致崩溃,整个进程也会因此崩溃。线程是进程的执行分支,一旦线程发生异常,就相当于进程发生异常,这将触发信号机制并终止进程。一旦进程终止,该进程内的所有线程也将随之退出。

二、Linux下进程与线程的区别

进程是资源分配的基本单位 线程是调度的基本单位

1.资源的区别

线程共享进程的数据,同时也有它们自己独立的数据部分:

线程ID

一组寄存器

errno

信号屏蔽字

调度优先级

进程中的多个线程共享同一个地址空间,因此Text Segment、Data Segment都是共享的。如果定义了一个函数,它可以在所有线程中被调用;如果定义了一个全局变量,它也可以在所有线程中被访问。除此之外,所有线程还共享其他进程资源和环境:

文件描述符表

每种信号的处理方式

当前工目录

用户id和组id

2.切换开销

从一个进程切换到另一个进程,需要保存当前进程的状态并恢复新进程的状态,这个过程称为上下文切换。而线程切换的开销较小,因为线程共享相同的地址空间,不需要改变地址空间的映射关系。

3.同步与通信

进程之间的通信需要进行进程间通信(IPC),如管道、消息队列等,这些方法效率较低。而线程之间的通信可以直接通过共享内存、全局变量等方式进行,效率较高。

4.单元性

进程是独立的执行单位,一个进程崩溃不会影响其他进程。而线程共享同一个进程的资源,一个线程崩溃可能导致整个进程崩溃。

三、Linux线程控制

1.POSIX线程库

POSIX线程库(POSIX Threads)也被称为pthread库,是一套线程创建、同步、调度和管理的标准接口,定义了在多线程程序中使用线程的API和语义。POSIX线程库通常用于UNIX和类UNIX操作系统(如Linux)上。

POSIX线程库的主要特点包括:

  1. 标准化接口: POSIX线程库定义了一套标准的线程操作接口,包括线程的创建、销毁、同步等操作,使得多线程程序能够以跨平台的方式编写。

  2. 线程管理: POSIX线程库提供了对线程的管理功能,包括线程创建、退出、同步、互斥锁、条件变量、信号量等。

  3. 线程调度: POSIX线程库定义了线程的调度接口,可以设置线程的调度策略、优先级和调度参数。

  4. 线程同步: 提供了各种线程同步的机制,确保多个线程之间的协作和同步。

在使用POSIX线程库时,需要包含 <pthread.h> 头文件,并链接 -lpthread 库。

2.创建线程

使用pthread_create()函数

函数声明:

参数解析

thread:返回线程ID

attr:设置线程的属性,attr为NULL表示使用默认属性

start_routine:是个函数地址,线程启动后要执行的函数

arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码

错误检查:

传统的一些函数在成功时返回0,在失败时返回-1,并且会对全局变量errno赋值以指示错误类型。

而pthreads函数在出错时不会设置全局变量errno(尽管大多数其他POSIX函数会这样做),它们会通过返回值来传递错误代码。

pthreads也提供了线程内的errno变量,以便支持其他依赖errno的代码。但对于pthreads函数的错误处理,建议通过检查返回值来判断,因为这比读取线程内的errno变量要节省开销。

示例代码:

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"

void *Printf(void *arg)
{
    
    printf("wohu!, 我是一个线程...\n");
    sleep(2);   
    
}



using namespace std;
int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, nullptr, Printf, nullptr);
    if(ret != 0)
    {
        cerr << " 线程创建错误" << endl;
        exit(EXIT_FAILURE);
    }
    pthread_join(pid,nullptr);


    return 0;
}

3、线程ID与虚拟地址空间的关系

pthread_create函数生成一个线程ID,存储在第一个参数所指的地址。这个线程ID与之前提到的不同。

先前提到的线程ID是进程调度的一部分。由于线程是轻量级的进程,是操作系统调度的基本单位,因此它需要一个唯一的数值来标识。

pthread_create函数的第一个参数指向一个虚拟内存单元,这个内存单元的地址就是新线程的线程ID,这属于NPTL线程库的范围。线程库的操作都是基于这个线程ID进行的。

NPTL线程库还提供了pthread_self函数,用于获取线程自身的ID。

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

4.线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1. 从线程函数中返回。这种方法不适用于主线程,因为从main函数中返回等同于调用exit。

2. 线程可以通过调用pthread_exit来结束自己。

3. 一个线程可以通过调用pthread_cancel来终止同一进程中的另一个线程。

5.线程等待

为什么需要线程等待?

已退出的线程,其空间未被释放,仍在进程地址空间内。

新创建的线程将不会复用之前退出线程的地址空间。

使用pthread_join来进行线程等待

调用该函数的线程会挂起等待,直到标识为thread的线程终止。thread线程的终止方式不同,通过pthread_join获得的终止状态也不同,具体如下:

1. 如果thread线程通过return返回,value_ptr所指向的单元中存放的是thread线程函数的返回值。
2. 如果thread线程被其他线程以pthread_cancel调用异常终止,value_ptr所指向的单元中存放的是常量PTHREAD_CANCELED。
3. 如果thread线程通过调用pthread_exit自行终止,value_ptr所指向的单元中存放的是传递给pthread_exit的参数。
4. 如果不关心thread线程的终止状态,可以向value_ptr参数传递NULL值。

6.线程分离

默认情况下,新创建的线程是可连接的。线程结束后,必须执行pthread_join操作以释放资源,否则可能导致系统资源泄露。如果不需要线程的返回值,join操作可能显得多余。在这种情况下,我们可以设置线程在退出时自动释放其资源。

使用pthread_detach进行线程分离

int pthread_detach(pthread_t thread);

也可以自己分离

pthread_detach(pthread_self());

四、Linux线程互斥

1.线程互斥的前置概念

临界资源:指多线程执行流中共享的资源。

临界区:是指在每个线程中访问临界资源的那部分代码。

互斥:确保任何时刻只有一个执行流能进入临界区访问临界资源,起到保护临界资源的作用。

原子性:指的是不可被任何调度机制中断的操作,这种操作只有完成或未完成两种状态。

2.互斥锁mutex

多个执行流同时访问同一个临界资源会带来一些问题,这个时候就需要用到锁。

2.1比如说下面这个简单的卖票场景:

四个线程同时卖100张票

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"
int t = 100;

void *tick(void *arg)
{
   
    while(1)
    {
        if(t < 0)
        {
           break;
        }
        else
        {
            usleep(1000);
            printf("线程%d卖: %d\n", pthread_self(), t);
            t--;
        }
    }
    return nullptr;
}

int main()
{
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1, nullptr, tick, nullptr);
    pthread_create(&t2, nullptr, tick, nullptr);
    pthread_create(&t3, nullptr, tick, nullptr);
    pthread_create(&t4, nullptr, tick, nullptr);
    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    pthread_join(t3,nullptr);
    pthread_join(t4,nullptr);


    return 0;
}

运行结果:

为什么会出现这样的情况?

1.if 语句判断条件为真以后,代码可以并发的切换到其他线程

2.usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

3.t-- 操作本身就不是一个原子操作

t--的汇编代码

可以看到t--是由三条汇编代码组成的,所以t--不是原子的。

要解决上述问题,需确保三个条件得到满足:

1.代码必须实现互斥,当一个线程进入临界区执行时,其他线程不得进入此临界区。

2.若多个线程同时请求执行临界区代码,而且临界区内没有线程在执行,则只能让一个线程进入该临界区。

3.若线程未在临界区内执行,则不应阻止其他线程进入临界区。

本质上就是加锁。

2.2互斥锁的接口

2.2.1初始化mutex

1.静态初始化

把mutex定义到全局区

2.使用init函数

2.2.2销毁mutex

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

2.2.2mutex加锁和解锁

成功返回0,失败返回错误码

调用 pthread_ lock 时,可能会遇到以下情况:

当互斥量处于未锁定状态时,该函数会锁定互斥量,并返回成功。

如果在调用函数时,互斥量已被其他线程锁定,或者有其他线程也在申请互斥量但未能成功竞争到,那么pthread_mutex_lock调用将会阻塞(即执行流被挂起),直到互斥量被解锁。

改进之前的代码:

加上锁

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"
int t = 100;

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

void *tick(void *arg)
{
   
    while(1)
    { 
        pthread_mutex_lock(&mtx);
        if(t < 0)
        {
            pthread_mutex_unlock(&mtx);
           break;
        }
        else
        {
            usleep(1000);
            printf("线程%d卖: %d\n", pthread_self(), t);
            t--;
            pthread_mutex_unlock(&mtx);
        }
    }
    return nullptr;
}

int main()
{
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1, nullptr, tick, nullptr);
    pthread_create(&t2, nullptr, tick, nullptr);
    pthread_create(&t3, nullptr, tick, nullptr);
    pthread_create(&t4, nullptr, tick, nullptr);
    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    pthread_join(t3,nullptr);
    pthread_join(t4,nullptr);


    return 0;
}

运行结果:

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

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

相关文章

基于51单片机的闭环反馈直流电机PWM控制电机转速测量( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机的闭环反馈直流电机PWM控制转速测量( proteus仿真程序设计报告原理图讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0086 1. 主要功能&#xff1a; 基于51单片机的闭环…

js宏任务微任务输出解析

第一种情况 setTimeout(function () {console.log(setTimeout 1) //11 宏任务new Promise(function (resolve) {console.log(promise 1) //12 同步函数resolve()}).then(function () {console.log(promise then) //13 微任务})})async function async1() {console.log(async1 s…

语音识别--使用YAMNet识别环境音

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

2024.5.7

//头文件#ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QLabel> #include <QTextToSpeech> #include <QString> #include <QtDebug> #include <QIcon> #i…

js浏览器请求,post请求中的参数形式和form-data提交数据时数据格式问题(2024-05-06)

浏览器几种常见的post请求方式 Content-Type 属性规定在发送到服务器之前应该如何对表单数据进行编码。 默认表单数据会编码为 "application/x-www-form-urlencoded" post请求的参数一般放在Body里。 Content-Type&#xff08;内容类型&#xff09;&#xff0c;一般…

截图工具Snipaste:不仅仅是截图,更是效率的提升

在数字时代&#xff0c;截图工具已成为我们日常工作和生活中不可或缺的一部分。无论是用于工作汇报、学习笔记&#xff0c;还是日常沟通&#xff0c;一款好用的截图工具都能大大提升我们的效率。今天&#xff0c;我要向大家推荐一款功能强大且易于使用的截图软件——Snipaste。…

CRC校验原理及步骤

文章目录 CRC定义&#xff1a;CRC校验原理&#xff1a;CRC校验步骤&#xff1a; CRC定义&#xff1a; CRC即循环冗余校验码&#xff0c;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#…

JUC-synchronized练习-交替打印ABC

今天来练习一下synchronized 简单来利用synchronized实现一个字符串的交替打印 主要的实现设置一个全局的变量state&#xff0c;线程执行通过不断累加state&#xff0c;根据state对三取余的结果来判断该线程是否继续执行还是进入等待。并通过synchronized锁住一个共享变量loc…

设计模式之模板模式TemplatePattern(五)

一、模板模式介绍 模板方法模式&#xff08;Template Method Pattern&#xff09;&#xff0c;又叫模板模式&#xff08;Template Pattern&#xff09;&#xff0c; 在一个抽象类公开定义了执行它的方法的模板。它的子类可以更需要重写方法实现&#xff0c;但可以成为典型类中…

学习R语言第五天

文章目录 语法学习创建数据的方式绘制图形的方式图形添加颜色如何操作数据的方式数据进行验算的判断加付值的方式修改变量名称的方式判断是否存在缺失值在计算的方式忽略缺失值通过函数的方式忽略缺失值日期处理的方式字符串转化成日期的方式格式化数据框中数据返回当前的日期的…

保研面试408复习 1——操作系统、计网、计组

文章目录 1、操作系统一、操作系统的特点和功能二、中断和系统调用的区别 2、计算机组成原理一、冯诺依曼的三个要点二、MIPS&#xff08;每秒百万条指令&#xff09;三、CPU执行时间和CPI 3、计算机网络一、各个层常用协议二、网络协议实验——数据链路层a.网络速率表示b.数据…

《十八》QThread多线程组件

本章将重点介绍如何运用QThread组件实现多线程功能。 多线程技术在程序开发中尤为常用&#xff0c;Qt框架中提供了QThread库来实现多线程功能。当你需要使用QThread时&#xff0c;需包含QThread模块&#xff0c;以下是QThread类的一些主要成员函数和槽函数。 成员函数/槽函数 …

Linux内核之获取文件系统超级块:sget用法实例(六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

EMAIL-PHP功能齐全的发送邮件类可以发送HTML和附件

EMAIL-PHP功能齐全的发送邮件类可以发送HTML和附件 <?php class Email { //---设置全局变量 var $mailTo ""; // 收件人 var $mailCC ""; // 抄送 var $mailBCC ""; // 秘密抄送 var $mailFrom ""; // 发件人 var $mailSubje…

如何查看慢查询

4.2 如何查看慢查询 知道了以上内容之后&#xff0c;那么咱们如何去查看慢查询日志列表呢&#xff1a; slowlog len&#xff1a;查询慢查询日志长度slowlog get [n]&#xff1a;读取n条慢查询日志slowlog reset&#xff1a;清空慢查询列表 5、服务器端优化-命令及安全配置 安…

6.Nginx

Nginx反向代理 将前端发送的动态请求有Nginx转发到后端服务器 那为何要多一步转发而不直接发送到后端呢&#xff1f; 反向代理的好处&#xff1a; 提高访问速度&#xff08;可以在nginx做缓存&#xff0c;如果请求的是同样的接口地址&#xff0c;这样就不用多次请求后端&#…

本地运行AI大模型简单示例

一、引言 大模型LLM英文全称是Large Language Model&#xff0c;是指包含超大规模参数&#xff08;通常在十亿个以上&#xff09;的神经网络模型。2022年11月底&#xff0c;人工智能对话聊天机器人ChatGPT一经推出&#xff0c;人们利用ChatGPT这样的大模型帮助解决很多事情&am…

AUTOSAR中EcuM、ComM和CanNm的关联

ComM的内外部唤醒 ComM可以通过NM保持网络的唤醒&#xff0c;同时也可以通过SM激活通信&#xff0c;总之就像一个通信的总管。 下面通过两种唤醒源来解释ComM的状态机。 1、内部唤醒 ① 当ComM上电初始化时会首先进入NO COMMUNICATION状态&#xff0c;在该状态下ComM会持续循…

Linux学习之路 -- 文件 -- 文件描述符

前面介绍了与文件相关的各种操作&#xff0c;其中的各个接口都离不开一个整数&#xff0c;那就是文件描述符&#xff0c;本文将介绍文件描述符的一些相关知识。 目录 <1>现象 <2>原理 文件fd的分配规则和利用规则实现重定向 <1>现象 我们可以先通过prin…

如何根据IP获取国家省份城市名称PHP免费版

最近项目遇到需要根据IP获取用户国家功能需求&#xff0c;网上找了一下&#xff0c;很多API接口都需要付费&#xff0c;考虑为公司节约成本&#xff0c;就取找找有没有开源的 github 上面那个包含多种语言&#xff0c;下面这个只有php&#xff0c;用法很简单 $ip 114.114.114…