【Linux】线程概念及线程互斥

目录

线程概念

线程优点

线程缺点

线程异常

线程系统编程接口

线程创建及终止

线程等待

使用线程系统接口封装一个小型的C++线程库并实现一个抢票逻辑

线程互斥

互斥量的接口

线程互斥实现原理

使用系统加锁接口封装LockGuard 实现自动化加锁

线程安全和可重入函数

常见线程安全和可重入情况

死锁

银行家算法简介


线程概念

线程是在进程内部,并且比进程更加轻量化的一种执行流。

线程是 CPU 调度的基本单位,而进程是承担系统资源的基本实体

一个进程内部可以包含多个线程,而在 LIinux 中,没有强制性的划分进程和线程的概念,线程也被叫做轻量级进程。

传统进程内部只有一个执行流;而进程内部创建了线程之后,内核中就有多个执行流了。

进程与线程的关系:

合理的使用多线程,能提高CPU执行密集型程序的执行效率和用户对IO密集型程序的使用体验!

CPU密集型程序指的是需要大量计算和处理的任务,涉及大量的数学运算、逻辑判断、数据处理等,对CPU的计算能力要求较高;IO密集型程序主要是指执行过程中需要大量的 IO 操作(大型文件多线程下载,涉及到大量的网络I/O操作和磁盘I/O操作,需要从网络中读取文件数据,并将其写入到本地磁盘中)。

CPU 是通过 PCB 对进程调度的,既然线程叫轻量级进程,那么在 Linux 中,CPU 调度线程的方式也是这样。但 进程 = 内核数据结构 + 代码和数据,相比于进程,线程就没有这么多资源了,线程共享进程的所有数据,但也有自己的数据:线程ID,一组寄存器,,errno,信号屏蔽字调度优先级,所以相比于进程,它的创建更简单。

各进程之间共享的进程资源和环境有:文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id。

线程在进程内部运行,本质是在进程地址空间内运行,所以对应调度所需要的寄存器少,且在轮转调度的时候不需要反复更新自己的线程上下文,从不需要更新缓存(cache)。

透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

虚拟地址通过页表化为物理地址详细过程

虚拟地址的32个比特位,前20个比特位是 “页表索引” ,这前10个比特位,形成了 2^10 - 1个页目录,这 2^10 - 1个页目录中,每个页目录中又存着 2^10 - 1个 “后10个比特位” 形成的数,这样计算的,页表一共可以映射 2 ^ 20 (1048576)个数,而这些数(页表码),每个数对应一个地址标识,,一个IO文件的基本大小 4KB(2^10 * 2^2),对这 2^10 个基本IO文件编号(叫页内偏移),所以这 2^20 个页表码,每个都对应着 2^10 个基本 IO 文件的大小(4KB),2^20 次方个 基本IO 文件的大小,加起来也就是我们常说的 4GB

因此,页表码 + 页内偏移 就能知道对应的文件位置! 

对这 1048576 个页码建立数据结构,那么,对页表的管理,就变成了对数据结构的增删查改!

有了这种页表划分逻辑,进程就可以将地址空间合理的分配给线程!

线程优点

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

线程缺点

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  • 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高

编写与调试一个多线程程序比单线程程序困难得,这里的难度主要指考虑一段代码需要考虑的情况更复杂。

线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃

线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。

线程系统编程接口

线程创建及终止

  • 创建一个新的线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

thread:输出型参数,将线程ID作为参数返回

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

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

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

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

  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面的线程ID属于进程调度的范畴,叫 LWP,只在系统内部使用,当一个进程中只有一个线程时,这个线程的 LWP 就等于进程的 PID 。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。


pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的

  • 线程终止

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

1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

2. 线程可以调用pthread_ exit终止自己。

3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit 函数可以获得进程的退出信息,存储在 value_ptr 指针中返回

void pthread_exit(void *value_ptr);

取消一个进程,成功返回0,失败返回错误码 

int pthread_cancel(pthread_t thread);// thread 为要终止线程的 id

线程等待

为什么要进行线程等待?

  • 线程终止后,内核资源并没有被释放,只有线程被等待成功后,内核资源才能被释放。
  • 等待可以得到线程的退出信息

pthread_join 接口可以等待线程结束

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

线程以不同的方式终止,用 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线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

使用线程系统接口封装一个小型的C++线程库并实现一个抢票逻辑

#include <pthread.h>
#include <functional>
#include <string>
#include <iostream>
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){}// 因为pthread 里的 函数指针 规定只能有一个 void* 类型的参数, 如果不定义为 static(类内方法)的话, 第一个参数永远是this,那么就不止一个参数了static void* Pthread_Routine(void* args)  {Thread* ts = static_cast<Thread*>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, Pthread_Routine, this);//将 this 指针传过去, 方便一会调用函数指针使用传进来的 pthread 数据if(n == 0){_isrunning = true;return true;}else return false;}bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0) //等待成功{_isrunning = true;return true;}return false;}bool IsRunning(){return _isrunning;}std::string ThreadName(){return _threadname;}~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

实现一个抢票逻辑

#include <iostream>
#include "pthread.hpp"
#include <cstdio>
#include <unistd.h>
std::string GetThreadName()
{static int Number = 0;char buffer[64];snprintf(buffer, sizeof(buffer), "thread-%d", ++Number);return buffer;
}
void Print(int i)
{std::cout << i << std::endl;
}
int ticket = 10000;
void GetTicket(std::string name)
{while(true){if(ticket > 0){usleep(100);printf("%s get a ticket %d\n", name.c_str(), ticket);ticket--;        }else break;}
}
int main()
{std::string name1 = GetThreadName();Thread<std::string> th1(name1, GetTicket, name1);std::string name2 = GetThreadName();Thread<std::string> th2(name2, GetTicket, name2);std::string name3 = GetThreadName();Thread<std::string> th3(name3, GetTicket, name3);std::string name4 = GetThreadName();Thread<std::string> th4(name4, GetTicket, name4);th1.Start();th2.Start();th3.Start();th4.Start();th1.Join();th2.Join();th3.Join();th4.Join();return 0;
}

运行结果:确实使用多线程并发完成了抢票,但依然出现了一些问题:

票竟然被抢到了 0 和负数,这是这些线程并发访问公共资源的时候产生的数据不一致问题

  • 为什么可能无法获得争取结果?
  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • --ticket 操作本身就不是一个原子操作(需要三步原子操作,这当中线程可能会被切换)

要解决上述问题,需要做到一下三点:  

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

本质就是系统需要一把锁将公共代码保护起来,Linux 将这把锁叫互斥量。

线程互斥

临界资源:在多线程编程中,被多个线程共享的一段代码或数据结构。这些资源在同一时间只能由一个线程访问。

临界区:在进程中访问临界资源的代码。

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

原子性:不会被任何调度机制打断的操作,改操作只有两态,要么完成,要么未完成。

互斥量的接口

  • 初始化互斥量

初始化互斥量有两种方法:

方法1,静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

方法2,动态分配:

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

mutex:要初始化的互斥量

attr:NULL

  • 销毁互斥量

销毁互斥量需要注意:

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

不要销毁一个已经加锁的互斥量,已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

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

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

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

发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

对互斥量加锁后,就能改进抢票系统了。

#include <iostream>
#include "pthread.hpp"#include <cstdio>
#include <unistd.h>class ThreadData
{
public:ThreadData(const std::string name, pthread_mutex_t* pmutex):_ThreadName(name),_mutex(pmutex){}
public:std::string _ThreadName;pthread_mutex_t* _mutex;
};
std::string GetThreadName()
{static int Number = 0;char buffer[64];snprintf(buffer, sizeof(buffer), "thread-%d", ++Number);return buffer;
}
void Print(int i)
{std::cout << i << std::endl;
}
int ticket = 10000;
void GetTicket(ThreadData* td)
{while(true){pthread_mutex_lock(td->_mutex);if(ticket > 0){usleep(10);printf("%s get a ticket %d\n",td->_ThreadName.c_str(),ticket);ticket--;        pthread_mutex_unlock(td->_mutex);}else {pthread_mutex_unlock(td->_mutex);break;}}
}
int main()
{pthread_mutex_t* mutex = new pthread_mutex_t;pthread_mutex_init(mutex, nullptr);std::string name1 = GetThreadName();ThreadData td1(name1, mutex);Thread<ThreadData*> th1(name1, GetTicket, &td1);std::string name2 = GetThreadName();ThreadData td2(name2, mutex);Thread<ThreadData*> th2(name2, GetTicket, &td2);std::string name3 = GetThreadName();ThreadData td3(name3, mutex);Thread<ThreadData*> th3(name3, GetTicket, &td3);std::string name4 = GetThreadName();ThreadData td4(name4, mutex);Thread<ThreadData*> th4(name4, GetTicket, &td4);th1.Start();th2.Start();th3.Start();th4.Start();th1.Join();th2.Join();th3.Join();th4.Join();return 0;
}

这样,当这段公共区代码被加锁后,解锁前其他的线程就不能访问这段代码了!

线程互斥实现原理

其实总结为一句话:当一个线程拿到开锁的钥匙之后,这个钥匙就通过 swap 变为了这个线程自己的上下文,就算线程被切换了,钥匙就被线程用上下文带走了,其他的线程依然无法打开这把锁!

使用系统加锁接口封装LockGuard 实现自动化加锁

#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 对象,就不在用加锁和解锁了,作用有点像智能指针,通过对象的建立和销毁来控制加锁和解锁!

线程安全和可重入函数

线程安全

  • 多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

可重入

  • 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

可重入与线程安全联系

函数是可重入的,那就是线程安全的;函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题;如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

可重入函数是线程安全函数的一种;线程安全不一定是可重入的,而可重入函数则一定是线程安全的。如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

常见线程安全和可重入情况

常见线程安全情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

要破坏死锁,只需要破坏死锁的几个必要条件之一即可!

避免死锁:在写代码时加锁顺序一致,尽量避免锁未释放的场景,尽量资源一次性分配。

资源一次性分配(One-time Allocation of Resources)是指在程序运行过程中,某个特定的资源只会被分配一次,并在之后不会再次分配。这种分配方式意味着一旦资源被分配给了一个实体(如进程或线程),它将一直保持分配状态,直到被释放。

还有一些可以避免死锁的算法:如死锁检测算法、银行家算法

银行家算法简介

银行家算法是一种用于避免死锁的资源分配算法,它是由Edsger W. Dijkstra在1965年提出的。银行家算法的目标是通过合理的资源分配来避免系统陷入死锁的状态。

银行家算法基于以下假设:

  1. 每个进程在开始执行之前必须声明其最大资源需求量。
  2. 系统中的资源数量是固定的。
  3. 系统能够检测到每个进程的资源请求和释放情况。

算法步骤:

  1. 初始化:为每个进程分配它所需要的最大资源量、已分配资源量和还需要资源量。同时初始化系统的可用资源量。
  2. 请求资源:当一个进程需要申请一定数量的资源时,首先检查系统的可用资源是否大于等于请求资源的数量,如果是,则进一步检查分配给该进程资源后是否仍然能够避免死锁。如果是,则分配资源给该进程,更新系统的可用资源量和进程的已分配和还需资源量。如果否,则进程需要等待。
  3. 执行任务:当一个进程执行完毕后,释放所有已分配资源,并将这些资源回收到系统的可用资源池中。
  4. 检查安全性:在每次资源请求和释放之后,系统需要进行安全性检查,判断系统是否处于安全状态。如果系统处于安全状态,则继续执行下一个进程的资源请求,否则,进程需要等待。

银行家算法的核心思想是,每个进程在申请资源时,系统需要先判断是否能够保证分配资源后,系统仍然处于安全状态。如果无法保证安全性,则不分配资源给进程,以避免死锁的发生。

银行家算法的优点是能够有效地避免死锁的发生,缺点是需要事先知道每个进程的最大资源需求量,且需要保持资源分配表的实时更新。同时,该算法可能导致资源利用率较低,因为只有当系统处于安全状态时,才会分配资源给进程。

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

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

相关文章

前端与后端协同:实现Excel导入导出功能

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

c# wpf template itemtemplate+dataGrid

1.概要 2.代码 <Window x:Class"WpfApp2.Window8"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend…

解析Apache Kafka:在大数据体系中的基本概念和核心组件

关联阅读博客文章&#xff1a;探讨在大数据体系中API的通信机制与工作原理 关联阅读博客文章&#xff1a;深入解析大数据体系中的ETL工作原理及常见组件 关联阅读博客文章&#xff1a;深度剖析&#xff1a;计算机集群在大数据体系中的关键角色和技术要点 关联阅读博客文章&a…

图像处理入门 3(how to get the pixel pitch / 如何获得单个像素的尺寸)

在这里一节里面&#xff0c;将记录如何获得一个相机传感器中单个像素点的尺寸&#xff0c;为了实现不同相机照片之间的匹配。 如果我们知道了相机传感器的尺寸和分辨率的大小&#xff0c;自然就可以求出单个像素的大小。 在这里插入图片描述&#xff1a; 如何获得相机传感器的…

【GEE实践应用】GEE下载遥感数据以及下载后在ArcGIS中的常见显示问题处理(以下载哨兵2号数据为例)

本期内容我们使用GEE进行遥感数据的下载&#xff0c;使用的相关代码如下所示&#xff0c;其中table是我们提前导入的下载遥感数据的研究区域的矢量边界数据。 var district table;var dsize district.size(); print(dsize);var district_geometry district.geometry();Map.…

Linux制作C++静态库和动态库并使用示例

创建动态库&#xff1a; 编写源文件&#xff1a; // sub.h 显式调用 #include <iostream>extern "C" int sub(int a, int b);// sub.cpp #include "sub.h"int sub(int a, int b) {return a - b; }// quadrature.h 隐式调用 #include <iostream&…

视频分块上传Vue3+SpringBoot3+Minio

文章目录 一、简化演示分块上传、合并分块断点续传秒传 二、更详细的逻辑和细节问题可能存在的隐患 三、代码示例前端代码后端代码 一、简化演示 分块上传、合并分块 前端将完整的视频文件分割成多份文件块&#xff0c;依次上传到后端&#xff0c;后端将其保存到文件系统。前…

6、【单例模式】确保了一个类在程序运行期间只有一个实例

你好&#xff0c;我是程序员雪球 在软件设计中&#xff0c;单例模式是一种常见的设计模式。它确保了一个类在程序运行期间只有一个实例&#xff0c;并提供了全局访问该实例的方式。单例模式在许多场景中都有广泛的应用&#xff0c;例如共享资源管理、数据库连接、日志记录器等…

多线程3

线程安全 线程可能会出现这些情况 导致两个线程不能达到自己想要去循环的次数&#xff0c;可能两个线程各10000&#xff0c;那么他们就会出现不到5000甚至不到5000的情况。 出现线程的不安全原因&#xff1a; 1.线程在系统中是随机调度,抢占式执行的.[线程不安全的, 罪魁祸首…

考研回忆录【二本->211】

备考时长差不多快一年半&#xff0c;从22年的11月底开始陆陆续续地准备考研&#xff0c;因为开始的早所以整个备考过程显得压力不是很大&#xff0c;中途还去一些地方旅游&#xff0c;我不喜欢把自己绷得太紧。虽然考的不是很好&#xff0c;考完我甚至都没准备复试&#xff0c;…

【软件工程】详细设计(一)

1. 引言 1.1 编写目的 该文档的目的是描述《学生成绩管理系统》项目的详细设计&#xff0c;其主要内容包括&#xff1a; 系统功能简介 系统详细设计简述 各个模块的实现逻辑 最小模块组件的伪代码 本文档的预期的读者是&#xff1a; 开发人员 项目管理人员 测试人员 …

docker容器技术篇:Docker API配置与常用操作

docker容器技术篇&#xff1a;Docker API配置与使用 一、API具体是什么&#xff1f; 百科解释应用程序接口&#xff08;API&#xff09;&#xff0c;又称为应用编程接口&#xff0c;就是软件系统不同组成部分衔接的约定&#xff0c;蒙了吧&#xff01;&#xff01;&#xff0…

解决沁恒ch592单片机在tmos中使用USB总线时,接入USB Hub无法枚举频繁Reset的问题

开发产品时采用了沁恒ch592&#xff0c;做USB开发时遇到了一个奇葩的无法枚举问题。 典型症状 使用USB线直连电脑时没有问题&#xff0c;可以正常使用。 如果接入某些特定方案的USB Hub&#xff08;例如GL3510、GL3520&#xff09;&#xff0c;可能会出现以下2种情况&#xf…

【NLP练习】中文文本分类-Pytorch实现

中文文本分类-Pytorch实现 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、准备工作 1. 任务说明 本次使用Pytorch实现中文文本分类。主要代码与文本分类代码基本一致&#xff0c;不同的是本次任务使用…

MyBatis 解决上篇的参数绑定问题以及XML方式交互

前言 上文:MyBatis 初识简单操作-CSDN博客 上篇文章我们谈到的Spring中如何使用注解对Mysql进行交互 但是我们发现我们返回出来的数据明显有问题 我们发现后面三个字段的信息明显没有展示出来 下面我们来谈谈解决方案 解决方案 这里的原因本质上是因为mysql中和对象中的字段属性…

【微服务】------核心组件架构选型

1.微服务简介 微服务架构&#xff08;Microservice Architecture&#xff09;是一种架构概念&#xff0c;旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦&#xff0c;从而降低系统的耦合性&#xff0c;并提供更加灵活的服务支持。 2.微服务技术选型 区域内容…

【零基础学数据结构】顺序表实现书籍存储

目录 书籍存储的实现规划 ​编辑 前置准备&#xff1a; 书籍结构体&#xff1a; 书籍展示的初始化和文件加载 书籍展示的销毁和文件保存 书籍展示的容量检查 书籍展示的尾插实现 书籍展示的书籍增加 书籍展示的书籍打印 书籍删除展示数据 书籍展示修改数据 在指定位置之前…

2024年第八届人工智能与虚拟现实国际会议(AIVR 2024)即将召开!

2024年第八届人工智能与虚拟现实国际会议&#xff08;AIVR 2024&#xff09;将2024年7月19-21日在日本福冈举行。人工智能与虚拟现实的发展对推动科技进步、促进经济发展、提升人类生活质量等具有重要意义。AIVR 2024将携手各专家学者&#xff0c;共同挖掘智能与虚拟的无限可能…

加速度:电子元器件营销网站的功能和开发周期

据工信部预计&#xff0c;到2023年&#xff0c;我国电子元器件销售总额将达到2.1万亿元。随着资本的涌入&#xff0c;在这个万亿级赛道&#xff0c;市场竞争变得更加激烈的同时&#xff0c;行业数字化发展已是大势所趋。电子元器件B2B商城平台提升数据化驱动能力&#xff0c;扩…

【机器学习】如何通过群体智慧解决机器学习的挑战“

机器学习的发展日新月异&#xff0c;但其成功实施的关键之一仍然是获取高质量的、标注良好的数据集。在这篇文章中&#xff0c;我们将探讨如何通过群体智慧来构建和改善机器学习的数据集&#xff0c;尤其是通过reCAPTCHA和带有目的的游戏&#xff08;Games with a Purpose, GWA…