Linux---多线程(上)

一、线程概念

  • 线程是比进程更加轻量化的一种执行流 / 线程是在进程内部执行的一种执行流
  • 线程是CPU调度的基本单位,进程是承担系统资源的基本实体

在说线程之前我们来回顾一下进程的创建过程,如下图

那么以进程为参考,我们该如何去设计创建一个线程呢?
线程不止一个,就注定它也需要被管理,即先描述在组织,即线程需要有一个TCB(和PCB类似),同时线程也是执行流,也需要被调度运行,被管理,所以我们还需要给它设计一系列的算法,然后还得让它和进程联系起来,这样很麻烦

那有没有更简单的做法呢?
由于线程同进程一样也是一个执行流,那么管理进程用的一些信息,线程也应该需要,只不过线程更加起轻量化而已(这个后面会有具体说明),所以进程的结构体和管理用的内核数据结构对于线程也应该同样适用,所以我们可以选择复用进程的代码和数据结构,轻松的完成任务,并且也不用考虑进程和线程的耦合问题,因为它们处在同一个体系框架下
(上面介绍的是Linux的做法,当然也有OS是将进程和线程分开来设计的)

具体如下

但是现在又出现了一个问题,如何看待进程???根据上面这张图,我们会发现进程和线程似乎一样了,都有数据结构和各自的代码和数据。下面我们来进一步理解进程

感性的理解进程和线程

我们可以将进程想象成一个家庭,线程则是家庭中的人,进程的任务就是将这个家变得越来越好,所以家中的每个人都要分工合作干好各自的事,每个家庭都具有独立性(进程独立性),即你生活的好不好跟你的邻居没太大关系,但是你们可能会有交集(进程间通信),之前我们讲的进程可以看成是家中只有一个人的情况。


下面写一段代码(不用管创建线程的函数,后面会讲,主要是观察现象)

#include <iostream>
#include <pthread.h>
#include <unistd.h>
//新线程
void *Threadroutine(void *args)
{
    char *p = (char *)args;
    while (1)
    {
        std::cout << "I am a new thread : " << p << ", pid : " << getpid() << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    //已经有进程了
    pthread_t tid;
    pthread_create(&tid, nullptr, Threadroutine, (void *)"thread1");
    //主线程
    while (1)
    {
        std::cout << "I am a main thread, pid : " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

首先确实有两个执行流在循环打印语句,并且它们的进程pid相同,但是它们的LWP(light weight process)不同,即它们是线程(在Linux中线程的底层是轻量级进程),所以OS在调度时看的是LWP,当然我们会发现有一个线程的LWP和PID是一样的,它就是主线程。(如果你看到乱序的打印也是正常的,因为OS如何调度两个线程是未知的)

而由于线程共享同一个进程地址空间,所以线程间的共享资源很多,也更容易通信(不保证安全)

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

int cnt = 0;
void *Threadroutine(void *args)
{
    char *p = (char *)args;
    while (1)
    {
        std::cout << "I am a new thread : " << p << ", pid : " << getpid() << " cnt:" << cnt << std::endl;
        cnt++;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Threadroutine, (void *)"thread1");
    sleep(1);

    pthread_t tid1;
    pthread_create(&tid1, nullptr, Threadroutine, (void *)"thread2");
    sleep(1);

    pthread_t tid2;
    pthread_create(&tid2, nullptr, Threadroutine, (void *)"thread3");
    sleep(1);

    while (1)
    {
        std::cout << "I am a main thread, pid : " << getpid() << " cnt:" << cnt << std::endl;
        sleep(1);
    }
    return 0;
}

注意:打印乱序是正常现象,因为Threadroutine是不可重入函数,但是我们重入了。但是这不妨碍我们能看到cnt在不断增加的,并且每个线程都能看到,也就是说cnt这个全局变量对于线程来说是共享的。

如何理解线程更加轻量化???

1、线程的创建更加简单

2、线程的切换更加高效

  • 要修改的寄存器少了---因为线程中有很多数据是一样的,比如存放页表地址的寄存器就不用修改,因为它们共用一个页表
  • 不需要重新更新cache(缓存)---根据局部性原理,在执行某条语句之后,更有可能执行它的上下文中的代码,所以我们会提前将它附近的代码和数据加载到缓存(称为热数据),来提高CPU效率,对于线程来说这样的热数据大概率是有效的,而对于进程来说,则是基本无效的,需要重新加载

(注意:这里谈论的线程切换是指在同一个进程中的线程的切换,不同进程的线程切换还是属于进程切换)

二、进一步理解进程地址空间

三、线程的优缺点+资源+异常

 1、优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

2、缺点

  • 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多(要更加细节)

3、资源

线程共享进程数据,但也拥有自己的一部分数据:线程ID、硬件上下文、栈、errno、信号屏蔽字、调度优先级

进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id

(注意:信号的pending位图、block位图都是各自私有的)
 

4、线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

四、线程控制---相关函数接口介绍

1、线程创建

功能:创建一个新的线程
参数:

  • thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数

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

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

void* ThreadRoutine(void * args)
{
    while(1)
    {
        std::cout << "I am a new thread" << std::endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,ThreadRoutine,nullptr);
    while(1)
    {
        std::cout << "I am a main thread" << std::endl;
        sleep(1);
    }
    return 0;
}

 

1)给线程传参的问题

#include <iostream>
#include <functional>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

using func_t = std::function<void()>;
class ThreadDate
{
public:
    std::string _name;
    u_int64_t _createtime;
    func_t _f;

public:
    ThreadDate(std::string name, u_int64_t createtime, func_t f)
        : _name(name), _createtime(createtime), _f(f)
    {
    }
};

void Print()
{
    std::cout << "我正在执行某个任务" << std::endl;
}

//函数的参数为void*,即可以传任意类型的指针,我们可以把结构体对象当参数传进去
void *ThreadRoutine(void *args)
{
    ThreadDate *ptd = static_cast<ThreadDate *>(args);
    while (1)
    {
        std::cout << "new thread name: " << ptd->_name << " , create time: " << ptd->_createtime << std::endl;
        ptd->_f();
        sleep(1);
    }
}

int main()
{
    std::vector<pthread_t> v;
    for (int i = 0; i < 5; i++)
    {
        char name[64] = {0};
        sprintf(name, "%s-%d", "thread", i);
        ThreadDate *p = new ThreadDate(name, (u_int64_t)time(nullptr), Print);
        pthread_t tid;
        pthread_create(&tid, nullptr, ThreadRoutine, (void *)p);
        v.push_back(tid);
    }

    sleep(3);
    std::cout << "thread id: ";
    for (auto x : v)
    {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    while (1)
    {
        std::cout << "I am a main thread" << std::endl;
        sleep(1);
    }
    return 0;
}

我们确实能通过函数的参数将我们想要的数据(放在结构体对象中)传给线程,并且也可以传函数,同理该线程的返回值也是一样(这个后面会演示)

打印出来的tid显然和LWP不相同,它具体是什么呢?(后面会说,这里只是抛出问题)。

也可以通过上面这个函数获取线程自身的tid,注意不是LWP!!!

2)线程异常问题

显然在第四个线程出现异常,收到信号后,整个进程都退出了,也说明线程的健壮性比较差

2、线程退出

原型:void pthread_exit(void *value_ptr)

功能:线程终止
参数:

  • value_ptr:value_ptr不要指向一个局部变量。

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

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

void* ThreadRoutine(void* args)
{
    int cnt = 5;
    while(cnt--)
    {
        std::cout << " I am a new thread " << std::endl;
        sleep(1);
    }
    
    pthread_exit(nullptr); // 终止线程
    // return nullptr; // 也可以终止线程
    // exit(1); // 注意:该函数是用来结束进程的!!!
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    //...
    return 0;
}

 我们也可以直接return,这样也是能终止线程的。

3、线程等待

线程退出默认也是需要被等待的(就像进程一样)

  • 线程退出,没有等待,会导致类似进程的僵尸问题
  • 线程退出,主线程也需要获取新线程的返回值

原型:int pthread_join(pthread_t thread, void **value_ptr)

功能:等待线程结束
参数

  • thread:线程ID
  • value_ptr(输出型参数):它指向一个指针,该指针指向线程的返回值,可以接收任意类型的指针

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

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

void* ThreadRoutine(void* args)
{
    int cnt = 5;
    while(cnt--)
    {
        std::cout << " I am a new thread " << std::endl;
        sleep(1);
    }
    
    pthread_exit((void*)"thread end"); //结束线程
    // return (void*)"thread end";
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    void*ret = nullptr;
    int n = pthread_join(tid,&ret);
    std::cout << "main thread, n: " << n << std::endl;
    std::cout << "new thread return val: " << (char*)ret << std::endl;
    return 0;
}

和线程创建时传给线程的参数一样,这里的返回值可以是任意类型的指针(可以指向结构体,该结构体中可以存放任何你想通过线程得到的数据,这个就不演示了,类比线程创建即可)

这里简单说明一下:为什么进程退出时既关心是否正常退出,又关心异常问题,但是线程出现异常我们并不关心?因为线程一旦异常,会导致进程整个挂掉,所以线程的异常就没必要关心了

4、线程分离

当主线程不关心线程的的返回结果时,我们可以将线程设置为分离状态,这样该线程结束后就会自动被OS回收,不需要主线程在等待了

int pthread_detach(pthread_t thread)

功能:分离线程

参数:

  • thread:线程ID

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

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

void* ThreadRoutine(void* args)
{
    // pthread_detach(pthread_self()); // 可以在线程中进行线程分离
    int cnt = 5;
    while(cnt--)
    {
        std::cout << " I am a new thread " << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    pthread_detach(tid);
    sleep(1);
    int n = pthread_join(tid,nullptr);
    std::cout << "main thread, n: " << n << std::endl;
    return 0;
}

显然线程等待失败,这里的线程分离,也可以在创建的线程中使用 (注意:线程分离后,出现异常还是会导致整个进程挂掉)

5、线程取消

原型:int pthread_cancel(pthread_t thread)

功能:取消一个执行中的线程
参数:

  • thread:线程ID

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

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

void* ThreadRoutine(void* args)
{
    while(true)
    {
        std::cout << " I am a new thread " << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    // pthread_detach(tid);
    sleep(5);
    int n = pthread_cancel(tid);
    std::cout << "main thread, cancel return: " << n << std::endl;

    void*ret = nullptr;
    n = pthread_join(tid, &ret);
    std::cout << "main thread, join return: " << n << " ret: " << (long long)ret << std::endl;
    return 0;
}

(注意:如果线程在pthread_cancel之前终止,那么该函数调用失败,返回错误码) 

thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED [#define PTHREAD_CANCELED ((void *) -1)]
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元里存放的是传给pthread_exit的参数
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源,即线程分离
  • 一个线程要么是joinable的,要么是分离的

五、理解线程库

在语言的角度:其实C++11中的线程库就是对原生线程库的封装,其他语句同理

注意:__thread只能修饰内置类型,自定义类型不行,在C++11中可以用thread_local对自定义类型进行修饰

六、模拟实现C++线程库(简易版)

#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <pthread.h>

using func_t = std::function<void()>; // 该函数类型可以按照需求改变
class thread
{
public:
    thread(std::string name, func_t f)
        : _tid(0), _name(name), _isrunning(false), _fun(f)
    {
    }

    // 注意,如果是非静态成员,则会多一个this作为参数(c++语法)
    static void *ThreadRoutine(void *args)
    {
        thread *t = static_cast<thread *>(args);
        t->_fun(); // 要想访问类成员,要传类对象
        return nullptr;
    }

    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
        if (n == 0)
        {
            _isrunning = true;
            return true;
        }
        return false;
    }

    bool Join()
    {
        if (!_isrunning)
            return true;
        int n = pthread_join(_tid, nullptr);
        if (n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }

    std::string getname()
    {
        return _name;
    }

    bool IsRunning()
    {
        return _isrunning;
    }
    
    ~thread()
    {
    }

private:
    pthread_t _tid;
    std::string _name;
    bool _isrunning;
    func_t _fun;
};


//进阶---用模板
template <class T>
using func_t = std::function<void(T)>;

template <class T>
class thread
{
public:
    thread(std::string name, func_t<T> f, T data)
        : _tid(0), _name(name), _isrunning(false), _fun(f),_data(data)
    {
    }

    // 注意,如果是非静态成员,则会多一个this作为参数(c++语法)
    static void *ThreadRoutine(void *args)
    {
        thread *t = static_cast<thread *>(args);
        t->_fun(t->_data); // 要想访问对象,要传递对象
        return nullptr;
    }

    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
        if (n == 0)
        {
            _isrunning = true;
            return true;
        }
        return false;
    }

    bool Join()
    {
        if (!_isrunning)
            return true;
        int n = pthread_join(_tid, nullptr);
        if (n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }

    std::string getname()
    {
        return _name;
    }

    bool IsRunning()
    {
        return _isrunning;
    }

    ~thread()
    {
    }

private:
    pthread_t _tid;
    std::string _name;
    bool _isrunning;
    func_t<T> _fun;
    T _data;// 如果需要也可以加一个成员变量存储线程的结果
};

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

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

相关文章

闭包表(Closure Table)存储和查询树形数据结构

闭包表通过在关系表中记录树节点之间的直接和间接关系来表示节点之间的层次结构&#xff0c;目的是支持高效的树遍历和查询操作。 一、创建闭包表 CREATE TABLE departments (id int NOT NULL COMMENT ID,name varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_…

Redis冲冲冲——Redis持久化方式及其区别

目录 引出Redis持久化方式Redis入门1.Redis是什么&#xff1f;2.Redis里面存Java对象 Redis进阶1.雪崩/ 击穿 / 穿透2.Redis高可用-主从哨兵3.持久化RDB和AOF4.Redis未授权访问漏洞5.Redis里面安装BloomFilte Redis的应用1.验证码2.Redis高并发抢购3.缓存预热用户注册验证码4.R…

掌握React中的useCallback:优化性能的秘诀

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

XWPFDocument中XmlCursor的使用

类名&#xff1a; org.apache.xmlbeans Interface XmlCursor版本&#xff1a; 原xml代码&#xff1a; <w:p w14:paraId"143E3662" w14:textId"4167FBA7" w:rsidR"001506F2" w:rsidRPr"003F3D89" w:rsidRDefault"001506F2&qu…

OpenStack安装步骤

一、准备OpenStack安装环境 1、创建实验用的虚拟机实例。 内存建议16GB&#xff08;8GB也能运行&#xff09;CPU&#xff08;处理器&#xff09;双核且支持虚拟化硬盘容量不低于200GB&#xff08;&#xff01;&#xff09;网络用net桥接模式 运行虚拟机 2、禁用防火墙与SELin…

2024会声会影永久免费版新功能软件特色及新功能

会声会影2024永久免费版是一款收到很多用户公认的极佳视频编辑软件&#xff0c;里面的每一个功能都特别的强悍你能够一键给图片视频添加特效非常的过瘾&#xff0c;赶快来一起下载试试吧。 会声会影2023-安装包&#xff1a; https://souurl.cn/gtyDFc 会声会影2023-安装包&…

Golang 开发实战day03 - Arrays Slices

Golang 教程03 - Arrays&#xff0c;Slices Go语言中的数组和切片都是用于存储数据的类型&#xff0c;但它们之间存在一些重要的区别。了解这些区别对于有效地使用它们至关重要。 1. Arrays 数组 1.1 定义 数组是一种固定大小的数据结构&#xff0c;用于存储相同类型的值。…

web基础05-jQuery

目录 一、jQuery 1.概述 2.原生js与jQuery对比 3.特点 4.使用 &#xff08;1&#xff09;入口函数 &#xff08;2&#xff09;语法 &#xff08;3&#xff09;jQuery选择器 5.方法 &#xff08;1&#xff09;获取属性值&#xff1a; &#xff08;2&#xff09;删除属…

TCP三次握手,四次挥手状态转移过程

1.TCP状态转移过程 TCP连接的任意一端都是一个状态机,在TCP连接从建立到断开的整个过程中,连接两端的状态机将经历不同的状态变迁.理解TCP状态转移对于调试网络应用程序将有很大的帮助. 2.三次握手状态转换 3.四次挥手状态转换 4.TIME WAIT状态详解 为什么要有一个"TIME…

【网络安全】-数字证书

数字证书 数字证书是互联网通讯中用于标志通讯各方身份信息的一串数字或数据&#xff0c;它为网络应用提供了一种验证通信实体身份的方式。具体来说&#xff0c;数字证书是由权威的证书授权&#xff08;CA&#xff09;中心签发的&#xff0c;包含公开密钥拥有者信息以及公开密…

c# 调用ip2region组件 根据ip地址进行定位归属地运营商

需求描述&#xff1a;当项目中需要将IP转换成对应的归属地以及运营商&#xff0c;那么通过ip2region组件即可完美实现。 p2region本身支持net4.5以上&#xff0c;还有个ip2region.net组件&#xff0c;它要求net6及以上。所以&#xff0c;根据自己项目的需求即可选择其中一种方…

【SpringCloud微服务实战03】Nacos 注册中心

一、Nacos安装 官方文档安装Nacos教程:Nacos 快速开始 这里安装的是1.4.7版本,安装之后访问http://127.0.0.1:8848/nacos 管理界面如下:(用户名:nacos,密码:nacos) 二、Nacos服务注册和发现 1、在父工程中配置文件pom.xml 中添加spring-cloud-alilbaba的管理依赖:…

使用Docker实现Jenkins+Python + Pytest +Allure 接口自动化

一、Jenkins搭建 参考《Docker 安装 Jenkins》 进入 jenkins 容器 CLI 界面 docker exec -itu root jenkins /bin/bash二、准备条件 1、替换镜像内源 为了安装wget&#xff0c;默认用yum会安装不上wget命令&#xff0c;参考文章《docker容器内如何更换yum源【只想换成国内…

准确识别APT,选对恶意代码检测系统最重要

通过APT检测出已知和未知恶意代码&#xff0c;提高网络安全主动防御能力&#xff0c;是网络安全解决方案中需要重视的地方。然而&#xff0c;目前业界普通的恶意代码检测系统难以准确识别APT&#xff0c;给政府、企事业单位的安全防护工作带来了极大困惑。 值得庆幸的是&#x…

深度学习Top10算法

自2006年深度学习概念被提出以来&#xff0c;20年快过去了&#xff0c;深度学习作为人工智能领域的一场革命&#xff0c;已经催生了许多具有影响力的算法。以下是深度学习top10算法&#xff0c;它们在创新性、应用价值和影响力方面都具有重要的地位。 1、深度神经网络&#xf…

ChatGPT等AI使用的过程苦笑不得瞬间

引言&#xff1a; 在人工智能的浪潮中&#xff0c;我们见证了技术的飞速发展和智能应用的广泛渗透。特别是随着语言模型的进步&#xff0c;AI如ChatGPT、文心一言、通义千问、讯飞星火等已经成为人们日常生活和工作中不可或缺的助手。然而&#xff0c;与任何新兴技术一样&#…

亚信安慧AntDB:守护数据世界的可靠堡垒

在信息时代的浪潮中&#xff0c;亚信安慧AntDB数据库不断进行细微的更新与精准修复&#xff0c;全力以赴提升性能和加固安全&#xff0c;确保系统运行的稳定性和可靠性。这样的持续维护不仅映射出系统的稳定性&#xff0c;也彰显了对用户体验的深切关怀。在数据的海洋中&#x…

vscode中解决驱动编写的时候static int __init chrdev_init()报错的问题

目录 错误出错原因解决方法 错误 在入口函数上&#xff0c;出现 expected a ; 这样的提示 出错原因 缺少了 __KERNEL __ 宏定义 解决方法 补上__KERNEL__宏定义 具体做法&#xff1a;在vscode中按下ctrlshiftp &#xff0c;输入&#xff1a;C/C:Edit Configurations&#xff0…

低功耗高端蓝牙智能跳绳解决方案

一、方案概述 跳绳运动作为轻量、燃脂、便捷的运动之一&#xff0c;拥有庞大的人群基础。在这样的趋势下&#xff0c;智能跳绳的智能化及精细化也就顺理成章。 芯联深入智能运动健康器材市场&#xff0c;最新开发了蓝牙智能跳绳方案&#xff0c;采用双霍尔高精准计数方案&…

strstr函数、chdir函数、access函数、strdup函数的介绍

1、strstr函数 strstr 是 C 语言中的一个字符串处理函数&#xff0c;用于在一个字符串中查找子字符串的第一次出现。 函数原型 char *strstr(const char *haystack, const char *needle); haystack&#xff1a;要在其中搜索的字符串。needle&#xff1a;要查找的子字符串。 函…
最新文章