Linux下的多线程编程:原理、工具及应用(1)

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

模拟语言封装Linux下多线程接口

线程互斥

前置知识

解释为什么会产生上述代码错误

如何解决?加锁!

什么是互斥锁?

pthread_mutex_t

pthread_mutex_init

PTHREAD_MUTEX_INITIALIZER

pthread_mutex_lock

pthread_mutex_unlock

pthread_mutex_trylock(不常用)

根据如上的互斥锁来进行操作

通过定义全局的锁

通过定义局部的锁(优雅的解决)


模拟语言封装Linux下多线程接口

        如下是我们以使用C++实现简单封装:

#pragma once
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <functional>template <class T>
using func_t = std::function<void(T)>;template <class T>
class Thread
{
public:Thread(const std::string threadname,func_t<T> func,T data):_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data){}static void* ThreadRoutine(void* args){Thread *ts=static_cast<Thread *>(args);ts->_func(ts->_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 ThreadName(){return _threadname;}bool Isrunning(){return _isrunning;}private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

        其中成员变量存储了进程线程的ID、线程名、线程运行状态、线程运行的函数以及线程传递的变量。实现了线程的构造函数(根据线程名、传入函数以及传入变量构造),实现了开始运行的操作、等待线程的操作、判断是否运行以及返回线程名的操作

        重点说一下ThreadRoutine这个函数!他实现的是配合运行操作为pthread_create传递函数在执行完后return nullptr结束线程的操作!那他为啥要定义成如下的形式呢?

    static void* ThreadRoutine(void* args){Thread *ts=static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}

        这是因为成员函数是默认会带有this指针的,而我们要传入pthread_create中规定了只能传入void* args变量的函数。因此我们使用static让他不具有this指针!当然,这并不是唯一的解决办法,我们也可以将他定义到类外,然后给在类内声明友元函数即可

线程互斥

        我们根据上面所封装的线程库来叙写了如下的代码:

#include "Thread.hpp"std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}int ticket = 10000; // 全局的共享资源void GetTicket(std::string name)
{while (true){if (ticket > 0) {// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", name.c_str(), ticket);ticket--;}else{break;}// 实际情况,还有后续的动作, TODO?}
}int main()
{std::string name1 = GetThreadName();Thread<std::string> t1(name1, GetTicket, name1);std::string name2 = GetThreadName();Thread<std::string> t2(name2, GetTicket, name2);std::string name3 = GetThreadName();Thread<std::string> t3(name3, GetTicket, name3);std::string name4 = GetThreadName();Thread<std::string> t4(name4, GetTicket, name4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();return 0;
}

        该代码简单的模拟了一个抢票的场景,其中票数仅为tickets=10000,在tickets减为0的时候就会停止抢票。我们创建4个线程来模拟多人抢票的情况。按照代码的原意,四个线程会依次抢票知道票数为0为止,但是代码真的如我们所想的那样嘛?

        从上面的现象我们很容易的发现我们的共享资源tickets发生了不该发生的操作,出现抢到同一张票以及多抢了的情况,出现了数据不一致的情况!这是为什么呢?下面先了解一些前置知识:

前置知识

        我们将任何一个时刻,只允许一个进程正在访问的资源称为临界资源。把进程中访问临界资源的代码叫做临界区。例如我们上述的代码:

        互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用 。图解如下:

        原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

 

解释为什么会产生上述代码错误

        请先看以下代码:

#include<iostream>int main()
{int a = 10;a++;return 0;
}

        很平常的一段代码,但是这也是我们常出现错误代码中访问临界资源的代码。我们可以从反汇编中看出问题:

        可以看到通过反汇编,我们发现本来是一段代码的操作,在底层居然被翻译成了三条操作。这也就违背了我们的原子性!这三条语句在多线程并发访问的时候都有可能会被中断!大致图解:

        当我们知道实际上访问临界资源时是有三步操作后,我们就可以理解为什么会产生如上的错误了!看完如下的例子就明白了:假设现在我们启动了两个线程,他们都要执行如上的三步。线程1先是访问该临界资源,但是在他执行完几次完整的访问(3步都走完)后,它的时间片用完了,恰好此时该线程卡在第二步的--操作,此时他要保存上下文,eax中存储的值为7,然后轮到下一个进程执行。下一个进程也是执行完整了几步,恰好也是在第二步的时候他的时间片用完了,此时他也要保存上下文,eax中存储的值为3。接着轮到第一个线程,他需要恢复上下文啊,因此,从第三步开始将eax返回内存中,此时!count又变回了7!!!

        看完上面的例子你大概就明白了,为什么上述代码互产生错误的原因。因为多线程并发访问全部int,不是原子的!!!会有数据不一致的并发访问问题!

        看完上述的解释是不是以为完了?当然没有!我们都知道在CPU中我们存在着:算术运算、逻辑运算、处理内外中断、控制单元的操作。在上述出错代码中我们还存在着if的判断语句,这就是一种逻辑运算,底层是需要两步的处理:1、加载如寄存器。2、判断他也不是原子的!也会出现会有数据不一致的并发访问问题!因此,我们根本就不知道每个进程的具体执行状况!这也是为什么该数据会减到负数的原因,因为有可能if判断都认为是符合条件的!但是实际寄存器中的值确是不符合的!

如何解决?加锁!

什么是互斥锁?

        互斥锁是一种同步机制,用于确保在多线程环境中共享资源的安全访问

        互斥锁的核心作用是防止多个线程同时访问和修改共享资源,从而避免数据竞争和不一致的问题。以下是互斥锁的一些关键特性和概念:

  1. 排他性:互斥锁确保在任何时刻,只有一个线程能够持有锁并访问共享资源。当一个线程获得锁时,其他线程必须等待直到锁被释放。
  2. 同步原语:互斥锁是一种同步原语,它通常与条件变量、信号量等其他同步机制一起使用,以实现复杂的线程间协作和通信。
  3. 初始化:在使用互斥锁之前,需要对其进行初始化,这可以通过pthread_mutex_init函数完成,也可以静态地通过PTHREAD_MUTEX_INITIALIZER宏来初始化一个互斥锁变量。
  4. 加锁与解锁:线程在访问共享资源前需要对互斥锁进行加锁(上锁),访问完成后需要释放互斥锁(解锁)。这一过程通常通过pthread_mutex_lockpthread_mutex_unlock函数来实现。
  5. 性能考量:互斥锁可能导致进程睡眠和唤醒,以及上下文切换,这些都会带来一定的性能开销。因此,互斥锁适用于加锁时间较长的场景,以减少频繁的锁争用和上下文切换。
  6. 销毁:当互斥锁不再使用时,应当通过pthread_mutex_destroy函数进行销毁,以避免资源泄漏。

pthread_mutex_t

    pthread_mutex_t是一个数据类型,用于表示互斥锁(Mutex)对象。在多线程编程中,互斥锁是一种同步机制,用于保护共享资源,防止多个线程同时访问和修改这些资源,从而避免数据竞争和不一致的问题。

   pthread_mutex_t类型的变量通常用于声明一个互斥锁对象,并使用pthread_mutex_init函数进行初始化。初始化后,可以使用pthread_mutex_lock函数对互斥锁进行加锁操作,使用pthread_mutex_unlock函数进行解锁操作。

        需要注意的是,在使用完互斥锁后,应该及时销毁它,以避免资源泄漏。

pthread_mutex_init

    pthread_mutex_init是一个用于初始化互斥锁的函数,它是POSIX线程库(Pthreads)中的一部分。互斥锁是一种同步原语,用于保护共享资源,防止多个线程同时访问。

        函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

        参数说明:

  • mutex:指向要初始化的互斥锁对象的指针。
  • attr:指向互斥锁属性对象的指针,可以设置为NULL,表示使用默认属性。

        返回值:

  • 成功时,返回0;
  • 失败时,返回一个非零错误码。

PTHREAD_MUTEX_INITIALIZER

    PTHREAD_MUTEX_INITIALIZER是一个宏定义,用于初始化一个互斥锁对象。它通常与静态分配的互斥锁变量一起使用,以确保在多线程环境中对共享资源的访问是安全的。

        该宏定义的作用是将互斥锁对象的值设置为默认状态,以便在程序启动时立即可用。具体来说,它将互斥锁对象的类型设置为pthread_mutex_t,并将其属性设置为默认值(通常是快速互斥锁)。

pthread_mutex_lock

    pthread_mutex_lock是一个函数,用于对互斥锁进行加锁操作。它的作用是确保在多线程环境中,只有一个线程可以访问共享资源,从而避免数据竞争和不一致的问题。

        函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_lock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码。如果互斥锁已经被其他线程持有,则该线程会被阻塞,直到互斥锁被释放为止。

        一旦线程成功获取互斥锁,其他试图获取该互斥锁的线程将会被阻塞,直到当前持有互斥锁的线程调用pthread_mutex_unlock函数释放互斥锁。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_unlock

    pthread_mutex_unlock是一个函数,用于对互斥锁进行解锁操作。它的作用是释放当前线程持有的互斥锁,以便其他线程可以获取该互斥锁并访问共享资源。

        函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_unlock函数时,它会尝试释放当前线程持有的互斥锁。如果当前线程确实持有该互斥锁,则该函数会成功释放互斥锁并返回0;否则,该函数会返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_trylock(不常用)

    pthread_mutex_trylock是一个函数,用于尝试对互斥锁进行加锁操作。它的作用是尝试获取互斥锁,如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_trylock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

根据如上的互斥锁来进行操作

通过定义全局的锁

        如下我们根据互斥锁来解决上述的问题:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"// 应用方的视角
std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}int ticket = 10000; // 全局的共享资源
// 共享资源了
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 锁就有了,被定义并初始化了,这个锁也是全局的哦!!// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
void GetTicket(std::string name)
{while (true){// 2. 是由程序员自己保证的!规则都必须先申请锁// 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!pthread_mutex_lock(&mutex); // 1. 申请锁本身是安全的,原子的,为什么?if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!{// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", name.c_str(), ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}// 实际情况,还有后续的动作, TODO?}
}int main()
{std::string name1 = GetThreadName();Thread<std::string> t1(name1, GetTicket, name1);std::string name2 = GetThreadName();Thread<std::string> t2(name2, GetTicket, name2);std::string name3 = GetThreadName();Thread<std::string> t3(name3, GetTicket, name3);std::string name4 = GetThreadName();Thread<std::string> t4(name4, GetTicket, name4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();return 0;
}

        可以看到我们成功的解决了上述的问题,但是加了互斥锁也让我们的执行速度相对于上面的执行速度变慢了许多。这是因为:当一个线程持有互斥锁时,其他试图访问相同共享资源的线程必须等待,直到锁被释放。这种等待会导致线程阻塞,减少了并行执行的机会。当线程在等待锁的过程中,操作系统可能会将其置于睡眠状态,并在锁可用时再次唤醒它。这种从睡眠到唤醒的过程涉及到上下文切换,这是一种相对耗时的操作。频繁的上下文切换会显著增加程序的执行时间。

通过定义局部的锁(优雅的解决)

        如下我们定义一个LockGuard.hpp的文件,该文件封装了一个可以通过构造以及析构完成对应的加锁以及解锁的操作:

#pragma once#include <pthread.h>// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex _mutex;
};

        主函数:

        可以看到我们在临界区中定义了上述LockGuard.hpp的lockguard变量,我们可以通过上述的构造以及析构进行加锁、解锁需要注意的是:临界区代码中可以发现我们使用了一个{}来括起来,这是表示代码块的意思,可以理解变量同在函数栈帧中一样。我们通过新定义的ThreadData类来传递给之前封装的多线程接口,优雅的实现了如下的代码:

#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"
#include "LockGuard.hpp"// 应用方的视角
std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}class ThreadData
{
public:ThreadData(const std::string &name, pthread_mutex_t *lock): threadname(name), pmutex(lock){}
public:std::string threadname;pthread_mutex_t *pmutex;
};int ticket = 10000; // 全局的共享资源void GetTicket(ThreadData *td)
{while (true){// 非临界区代码!// 2. 是由程序员自己保证的!规则都必须先申请锁// 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!{LockGuard lockguard(td->pmutex);if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!{// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", td->threadname.c_str(), ticket);ticket--;}else{break;}}// 非临界区代码!// 实际情况,还有后续的动作, TODO?}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);std::string name1 = GetThreadName();ThreadData *td = new ThreadData(name1, &mutex);Thread<ThreadData *> t1(name1, GetTicket, td);std::string name2 = GetThreadName();ThreadData *td2 = new ThreadData(name2, &mutex);Thread<ThreadData *> t2(name2, GetTicket, td2);std::string name3 = GetThreadName();ThreadData *td3 = new ThreadData(name3, &mutex);Thread<ThreadData *> t3(name3, GetTicket, td3);std::string name4 = GetThreadName();ThreadData *td4 = new ThreadData(name4, &mutex);Thread<ThreadData *> t4(name4, GetTicket, td4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();pthread_mutex_destroy(&mutex);delete td;delete td2;delete td3;delete td4;return 0;
}

        一些知识点汇总:

// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
// 3. 个别系统,抢票代码会出现很多的票被同一个线程抢完了
// 4. 多线程运行,同一份资源,有线程长时间无法拥有,饥饿问题
// 5. 要解决饥饿问题,要让线程执行的时候,具备一定的顺序性 --- 同步

 


                         感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【项目设计】基于Httplib和Mysql的视频播放

项目源码&#xff08;绝对可以直接运行&#xff09; 一、项目介绍 1. 对视频播放系统的认识 搭建视频共享播放服务器&#xff0c;可以让所有人通过浏览器访问服务器&#xff0c;实现视频的上传查看&#xff0c;以及管理并播放的功能。主要是完成服务器端的程序业务功能的实现…

vue 引用百度地图

address.vue <template><div><!-- 地图 --><el-drawer:visible.sync"type1"direction"rtl"size"50%"append-to-bodyclass"map-drawer":before-close"beforeClose"><div style"width: 100%…

永热爱 敢向前 | Paraverse平行云的2023 年终总结

永热爱&#xff0c;敢向前 值此新年&#xff0c;回顾2023&#xff0c;仅以此句&#xff0c;献给所有XR产业信仰者 2023 年&#xff0c;是XR产业技术和场景承上启下的关键之年 在这场波澜壮阔的技术潮中 「Paraverse平行云」踏浪前行 已是第八个年头&#xff0c;让我们一起…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的远距离停车位检测系统(深度学习代码+UI界面+训练数据集)

摘要&#xff1a;开发远距离停车位检测系统对于提高停车效率具有关键作用。本篇博客详细介绍了如何运用深度学习构建一个远距离停车位检测系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并对比了YOLOv7、YOLOv6、YOLOv5&#xff0c;展示了不…

2024 MCM数学建模美赛2024年A题复盘,思路与经验分享:资源可用性与性别比例 | 七鳃鳗的性别比例变化对生态系统稳定性的影响(四)

审题 第三问要我们评估七鳃鳗的性别比例变化对生态系统稳定性的影响。 这里我们就要去查一下生态系统稳定性的定义。 通过查资料我们知道&#xff0c;生态系统稳定性包括生态系统的抵抗力和恢复力。 OK&#xff0c;到这里问题就变成了&#xff0c;七鳃鳗的性别比例对生态系…

LeetCode2115. 从给定原材料中找到所有可以做出的菜

拓扑排序 题面 题目链接&#xff1a;2115. 从给定原材料中找到所有可以做出的菜 - 力扣&#xff08;LeetCode&#xff09; 你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。第 i 道菜的名字为 recipes[i] &#xff0c;如果你有它 所有…

游戏免费下载平台模板源码

功能介绍 此游戏网站模板源码是专门为游戏下载站而设计的&#xff0c;旨在为网站开发者提供一个高效、易于维护和扩展的解决方案。 特点&#xff1a; 响应式设计&#xff1a;我们的模板可以自适应不同设备屏幕大小&#xff0c;从而为不同平台的用户提供最佳的浏览体验。 …

Qt中进行客户端开发框架

在Qt中进行客户端开发是一种常见的做法&#xff0c;Qt是一个跨平台的C框架&#xff0c;提供了丰富的工具和类库&#xff0c;用于开发图形用户界面&#xff08;GUI&#xff09;应用程序、网络应用程序以及其他类型的软件。以下是一些常用的Qt客户端开发框架和技术&#xff0c;希…

php.exe运行时,提示缺少VCRUNTIME140.dll

php.exe运行时&#xff0c;提示缺少VCRUNTIME140.dll 下载地址 https://www.microsoft.com/zh-cn/download/details.aspx?id48145根据需要选择下载3.运行安装后&#xff0c;再次运行php.exe。

已解决:android SDK安装时点击SDK Manager出现闪退

1、首先确保电脑里边安装了JDK&#xff0c;并且要把安装路径配置在环境变量里边&#xff0c;避免使用绝对路径 推荐%JAVA_HOME%\bin 2、在C:\Users\huanhuan\Desktop\android-sdk-windows\tools路径下找到android.bat文件打开&#xff0c;把set java_exe后改为jdk中java.exe的路…

TCP的三次握手和4次挥手

一、首先讲一下TCP的由来 最开始&#xff0c;人们考虑到将网络信息的呼唤与回应进行规范&#xff0c;达成一种公认的协议&#xff0c;就好像没有交通规则的路口设定交通规则。 人们设计出完美的OSI协议&#xff0c;这个协议包含七个层次由下到上分别是&#xff1a; 物理层&…

7-Eleven用工数字化:零售哲学下的人效管理实践

2014年&#xff0c;一本《零售的哲学》在中国掀起热潮&#xff0c;揭示了7-Eleven便利店的新零售坪效管理秘诀。而对大部分零售企业来说&#xff0c;劳动力效率是坪效背后的主要支柱。近期&#xff0c;国内领先的劳动力管理云服务提供商盖雅工场发布了《聚焦人效、重塑组织&…

计算机毕业设计-神经网络算法及对未来一月的天气状况预测系统

概要 随着对气象各项数据的观测手段、技术上的提升&#xff0c;对于各项或取得数据种类&#xff0c;精度上都有着更好的超越&#xff0c;而对于气象温度进行预测是目前预测数据中最重要的需要解决的问题之一。 针对如何选择预测一个月内的天气情况&#xff0c;本次利用神经网络…

最新骨传导耳机热门评测!南卡、韶音和墨觉哪个最好?

最近有不少朋友向我寻求建议&#xff0c;关于如何挑选优质的骨传导耳机。作为一名经验丰富的骨传导耳机爱好者&#xff0c;我自然愿意为大家分享一期详尽的评测指南。在选择骨传导耳机时&#xff0c;音质表现和佩戴的舒适度是最为关键的评价标准。 如今市场上存在很多劣质产品&…

基于有限状态机开发健壮的Nodejs/TCP客户端

有限状态机是一种数学计算模型&#xff0c;它描述了在任何给定时间只能处于一种状态的系统的行为。形式上&#xff0c;有限状态机有五个部分&#xff1a; 初始状态值 (initial state)有限的一组状态 (states)有限的一组事件 (events)由事件驱动的一组状态转移关系 (transition…

分析型数据库的主要使用场景有哪些?

如今数据已经成为了企业和组织的核心资产。如何有效地管理和利用这些数据&#xff0c;成为了决定竞争力的关键。分析型数据库作为数据处理领域的重要工具&#xff0c;为各行各业提供了强大的数据分析和洞察能力。基于分析型数据库&#xff08;Apache Doris &#xff09;构建的现…

Linux中GPU相关命令

Linux查看显卡信息&#xff1a; lspci | grep -i vga 使用nvidia GPU可以&#xff1a; lspci | grep -i nvidia1 前边的序号 "00:0f.0"是显卡的代号(这里是用的虚拟机); 查看指定显卡的详细信息用以下指令&#xff1a; lspci -v -s 00:0f.01 Linux查看Nvidia显…

MongoDB实战面试指南:常见问题一网打尽

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! MongoDB是一款流行的非关系型数据库&#xff0c;以其高效、可扩展的特性受到开发者的青睐。了解MongoDB的架构、存储引擎和数据结…

Jmeter进行http接口测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 本文主要针对http接口进行测试&#xff0c;使用 jmeter工具实现…

vue3+ts props定义识别为unknown

"vue": "^3.3.4", "typescript": "5.0.4", 确保agriculturalPollution引入成功确保PropType引入成功details获得类型推断defineProps传参正确props的detail为unknown 这就很奇怪&#xff0c;一步步都是按照规范写的&#xff0c;但是…