当前位置: 首页 > news >正文

Linux : 多线程互斥

目录

一  前言

二 线程互斥

  三  Mutex互斥量

1. 定义一个锁(造锁) 

2. 初始化锁

3. 上锁

4. 解锁

5. 摧毁锁

四 锁的使用

五 锁的宏初始化 

六 锁的原理

1.如何看待锁?

2. 如何理解加锁和解锁的本质 

七 c++封装互斥锁

八 可重入与线程安全 

1. 可重入与线程安全联系

2. 可重入与线程安全区别

九 死锁

1.死锁产生的必要条件

2.死锁的避免方法


一  前言

我们在上一章节Linux: 线程控制-CSDN博客学习了什么是多线程,以及多线程的控制和其优点,多线程可以提高程序的并发性和运行效率。但是多线程控制也有一定缺点,例如有些多线程的程序运行结果是有一些问题的,如出现了输出混乱、访问共享资源混乱等特点。所以我们下面提出的这个概念是关于保护共享资源这方面的——线程互斥。


二 线程互斥

在正式认识线程互斥之前,我们先来介绍几个概念:

  • 临界资源:多线程执行流共享的资源(且这个资源是被保护的)就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么不做,要么做完。(原子性针对的是操作)。

接下来我们用一个测试来学习多线程访问共享资源可能带来的问题,以及如何解决。

🚀:系统调用接口大多都是用c接口,我们通过c/c++混编的方式对线程的创建以及等待进行封装

//makefile/
mythread:mythread.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <cstdio>
#include <cassert>///Thread.hpp/头文件///
class Thread; //对类进行声明//上下文
class Context
{
public:Thread* this_;void* args_;
public:Context():this_(nullptr),args_(nullptr){}~Context(){}
};
//对一个线程进行封装
class Thread
{
public:typedef std::function<void*(void*)> func_t;const int num =1024;
public://1.构造函数,完成对线程的创建Thread(func_t func, void* args, int number):func_(func),args_(args)//c++自带类型func_,所以可以用拷贝构造func_(func){char buffer[num];//字符数组,char* buffersnprintf(buffer, sizeof(buffer), "thread->%d", number);name_=buffer;Context* ctx=new Context();ctx->this_=this;ctx->args_=args_;int n=pthread_create(&tid_,nullptr,start_routine,ctx);assert(n==0);(void)n;}static void* start_routine(void* args){//静态方法不能调用成员变量//return func_(args);Context* ctx=static_cast<Context*>(args);void* ret=ctx->this_->run(ctx->args_);delete ctx;return ret;}//线程等待void join(){int n=pthread_join(tid_,nullptr);assert(n==0);(void)n;}void* run(void* args){return func_(args);}private:std::string name_;pthread_t tid_;func_t func_;//实现方法void* args_;
};
#include <iostream>
#include <unistd.h>
#include <memory>
#include "Thread.hpp"
//测试用例//
int tickets=10000;
void* getTickets(void* args)
{std::string username=static_cast<const char*>(args);while(true){ if(tickets >0){//usleep(1244);std::cout<< username <<"我正在抢票"<<tickets--<< std::endl;usleep(1244);}else{break;}}return nullptr;
}
int main()
{std::unique_ptr<Thread> thread1(new Thread(getTickets,(void*)"user1",1));std::unique_ptr<Thread> thread2(new Thread(getTickets,(void*)"user2",2));std::unique_ptr<Thread> thread3(new Thread(getTickets,(void*)"user3",3));std::unique_ptr<Thread> thread4(new Thread(getTickets,(void*)"user4",4));thread1->join();thread2->join();thread3->join();thread4->join();
}

测试结果

 🚌:接下来我们对代码进行一定改动,再看一下运行结果

 while(true){ if(tickets >0){usleep(1244);//现在我们在进行tickets--操作之前让线程进行休眠//再来看看运行结果会和之前的一样吗?std::cout<< username <<"我正在抢票"<<tickets--<< std::endl;//usleep(1244);//之前的代码是在这里进行了休眠,}else{break;}}

从结果来看,我们放票了10000张照片,而竟然抢到了-2张票,明显不合理。接下来我们回答一下为什么会出现这种现象? 

 代码中凡是关于算数计算的问题,实际上都是交给CPU进行执行的,这里面包括了加减乘除、逻辑运算、逻辑判断,最终都由CPU来解决的。对变量tickets进行--,看起来只有一条语句,但是汇编至少是三条语句,即cpu 会对 tickets-- 的操作会分成三步来执行 

  1. 从内存读取数据到cpu寄存器中
  2. 在寄存器中让cpu进行对应的逻辑判断和运算
  3. 将新的结果写到内存中变量的位置

接下来我们用下面图进行说明,为了方便,假设我们只有两个线程。

 

上面就是该程序出错的原因,其主要原因是在判断 tickets>0 由于会调用其他线程,从而使得错误发生在 tickets-- 操作上,对票的数量修改产生混乱。 

 🚴造成这种结果的原因是什么呢?

  1. 我们对共享资源的访问和修改都不是原子的(即没有做完),这两个操作都会存在中间态,即CPU在计算的过程中需要读取、计算、返回等多个操作,一旦CPU执行某个线程处在某个中间状态的时候暂停了,其他线程可能会“趁虚而入”。
  2. 存在多个线程同时访问共享资源的情况。

  三  Mutex互斥量

 了解了程序出现问题的原因,下来我们就讨论如何解决它:我们先从如何防止多个线程同时访问共享资源开始

       代码必须要有互斥行为:当一个线程访问并执行共享资源的代码时,其他线程不能进入

要想使线程具有互斥行为,我们要引出一个关键工具——,通过给执行共享资源区上一把锁,从而阻止其他线程进入,这种锁被称为互斥锁,给予代码互斥的效果。

 锁的接口及其使用

pthread 库为我们提供了 “定义一个锁”、“初始化一个锁   “上锁”、“解锁”、“销毁一个锁” 的接口:

1. 定义一个锁(造锁) 

pthread_mutex_t  是一个类型,可以来定义一个互斥锁。就像定义一个变量一样使用它定义互斥锁的时候,锁名可以随便设置。互斥锁的类型 pthread_mutex_t 是一个联合体。 

2. 初始化锁

pthread_mutex_init( ) 是pthread库提供的一个初始化锁的一个接口,第一个参数传入的就是需要初始化的锁的地址。 第二个参数需要传入锁初始化的属性,在接下来的使用中暂时不考虑,使用默认属性即传入nullptr 。成功返回0,否则返回错误码。 

3. 上锁

pthread_mutex_lock() ,阻塞式上锁,即 线程执行此接口,指定的锁已经被锁上了,那么线程就进入阻塞状态,直到解锁之后,此线程再上锁。当上锁成功,则返回0,否则返回一个错误码。

4. 解锁

pthread_mutex_unlock() ,作用是解锁接口,一般用于出了执行共享资源区的时候。当解锁成功,返回0,否则返回一个错误码。

5. 摧毁锁

pthread_mutex_destroy 是用来摧毁定义的锁,参数需要传入的是需要摧毁的锁的指针。成功则返回0,否则返回错误码。


四 锁的使用

#include <iostream>
#include <unistd.h>
#include <memory>
#include <vector>
#include "Thread.hpp"class ThreadData
{
public:ThreadData(const std::string & threadname,pthread_mutex_t* mutex_p):threadname_(threadname), mutex_p_(mutex_p){}~ThreadData(){}
public:std::string threadname_;pthread_mutex_t* mutex_p_;//锁的指针
};
int tickets=10000;
void* getTickets(void* args)
{//std::string username=static_cast<const char*>(args);//加锁和解锁是多个线程串行执行的,程序变慢了。ThreadData* td=static_cast<ThreadData*>(args);while(true){ //加锁pthread_mutex_lock(td->mutex_p_);if(tickets >0){usleep(1244);std::cout<< td->threadname_ <<"我正在抢票"<<tickets<< std::endl;tickets--;   pthread_mutex_unlock(td->mutex_p_);//解锁// usleep(1244);}else{  pthread_mutex_unlock(td->mutex_p_);//解锁break;}}return nullptr;
}int main()
{#define NUM 4pthread_mutex_t lock;//定义一个锁pthread_mutex_init(&lock,nullptr);//初始化一个锁//接下来我们如何把这个锁以及一些参数传递给线程呢?我们创建一个类ThreadDatastd::vector<pthread_t> tids(NUM);for(int i=0;i<NUM;i++){char buffer[64];snprintf(buffer,sizeof buffer,"thread %d",i+1);ThreadData* td=new ThreadData(buffer,&lock);//用的同一把锁pthread_create(&tids[i],nullptr,getTickets,td);}for( const auto &tid:tids){pthread_join(tid,nullptr);}pthread_mutex_destroy(&lock);//销毁锁return 0;}

测试结果 

可以看到同过加锁的操作对共享资源的代码进行加锁保护之后,程序已经能正常的进行抢票了。但是我们又发现一个问题,那就是都是线程4进行抢票,这是为什么呢? 

🚩:锁只规定互斥访问,没有规定谁优先执行 ,接下里我们通过修改一下代码,来进行测试。
 通过usleep(1000),线程在访问加锁的资源之后,进行休眠即阻塞状态,这个时候cpu会对其他线程进行随机调度,从而实现了多个进程对保护的共享资源进行抢票的过程 。

while(true){ //加锁pthread_mutex_lock(td->mutex_p_);if(tickets >0){usleep(1244);std::cout<< td->threadname_ <<"我正在抢票"<<tickets<< std::endl;tickets--;   pthread_mutex_unlock(td->mutex_p_);//解锁// usleep(1244);}else{  pthread_mutex_unlock(td->mutex_p_);//解锁break;}//抢完票之后,我们设置一个任务。例如形成订单usleep(1000);//形成一个订单给用户//通过usleep(1000),线程在访问加锁的资源之后,进行休眠即阻塞状态,这个时候cpu会对其他线程进行随机调度//从而实现了多个进程对保护的共享资源进行抢票的过程}


五 锁的宏初始化 

在上面我们已经简单学习了锁的使用,关于锁的初始化上面用到的是pthread库提供的接口:pthread_mutex_init() ,但是在系统中还存在另一种初始化锁的方法,还方法只针对全局锁进行初始化使用该宏初始化的锁是不需要手动销毁的,即不需要我们调用 pthread_mutex_destroy() 接口

下面演示该宏定义的全局锁的使用:

int tickets=10000;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//宏定义的全局锁
void* getTickets(void* args)
{//加锁和解锁是多个线程串行执行的,程序变慢了。std::string username=static_cast<const char*>(args);//ThreadData* td=static_cast<ThreadData*>(args);while(true){ //加锁pthread_mutex_lock(&lock);//加锁直接取地址&lockif(tickets >0){usleep(1244);std::cout<< username<<"我正在抢票"<<tickets<< std::endl;tickets--;   pthread_mutex_unlock(&lock);//解锁// usleep(1244);}else{  pthread_mutex_unlock(&lock);//解锁break;}//抢完票之后,我们设置一个任务。例如形成订单usleep(1000);//形成一个订单给用户return nullptr;
}int main()
{//创建四个线程pthread_t t1, t2, t3, t4;pthread_create(&t1,nullptr,getTickets,(void*)"thread 1");pthread_create(&t2,nullptr,getTickets,(void*)"thread 2");pthread_create(&t3,nullptr,getTickets,(void*)"thread 3");pthread_create(&t4,nullptr,getTickets,(void*)"thread 4");pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);pthread_join(t4,nullptr);//不需要手动销毁锁了即pthread_mutex_destroy(&lock,nullptr) }

测试结果:

接下来我们对几个概念进行再次说明一下:

  • 临界资源:多线程执行流共享的资源(且这个资源是被保护的)就叫做临界资源,例如上文代码的共享资源tickets通过加锁被保护,叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区,例如上文加锁和解锁的中间部分即对tickets进行--的代码区叫做临界区

六 锁的原理

前边说了这么多有关于锁的介绍,那么我们该如何看待锁呢?

1.如何看待锁?

  •  a.  多个线程都能利用锁,即:锁本身就是一个共享资源,既然是共享资源,那么共享资源就要被保护?锁的安全谁来保护呢
  •  b.  锁是共享资源需要被保护,那么加锁这个操作就是原子性的(即要么加锁成功,要么加锁不成功)
  •  c. 加锁如果申请成功,就继续向后执行,如果申请暂时没有成功,执行流会进行阻塞。
  • d. 谁持有锁,谁进入临界区。

如果线程1,申请成功,进入临界资源,正在访问临界资源期间,其他线程只能阻塞等待。

如果线程1,申请成功,进入临界资源,正在访问临界资源期间,那么线程1可以被cpu进行切换吗?答案是可以的,但是当持有锁的线程被切走的时候,即使自己被切走了,其他线程

依然无法继续申锁成功,也便无法继续向后执行,直到线程1释放了锁,其他线程才能申请锁成功,继续往后执行。

2. 如何理解加锁和解锁的本质 

  • 经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,所以保证了原子性,

针对上面的伪代码我们对加锁和解锁进行分析 

首先, al 表示寄存器, mutex 则表示在内存中的锁   (mutex互斥量可以理解为就是一个锁)

movb $0, %al, 把 0 存入 al 寄存器中

xchgb %al, mutex, 交换 al寄存器 和 内存中mutex 的数据

if(al > 0) { return 0; }, 如果 al 寄存器中的数据 大于 0, 则 申请锁成功, 返回 0. 否则, 就阻塞等待.

整个上锁函数执行的语句可以看作这几个过程.  其中, xchgb %al, mutex 操作 是实际上锁的操作.

我们用图来描述, 如果线程1 在执行上锁的操作。

🚲如果没有上锁时, 锁的值是1.那么 执行 xchgb %al, mutex 将 al 中的0 与 mutex 的值交换,

此时 al中的值变为1,这个时候其实以及申锁成功了,如果线程没有被cpu切走,那么

if(al>0)满足,线程就执行后续语句。

al中的值变为1,这个时候表示申锁成功了,但是线程还没往下执行就被cpu切走了,那么后面的线程也不可能申锁成功执行后续代码,这是因为cpu中寄存器对线程进行切换的时候,会把寄存器中关于线程的上下文切走。所以当下一个线程来的时候,寄存器会把新线程的al=0与内存中的mutex=0交换,al的值还是0,不会继续执行,进入阻塞状态,所以此时cpu又会对

上一个线程调度,cpu对线程加载的时候,会把线程上下文重新加载过来,即al=1,所以执行后续代码,当执行完相应的临界区的时候,寄存器再将al=1 与mutex交换,这就是解锁过程,此时mutex=1,后面的线程才有可能申请锁成功,上面的分析说明了加锁和解锁是个二原性行为,保证了共享资源不会被多个线程同时执行,即只能串行运行。


七 c++封装互斥锁

系统调用接口大多采样c接口,c语言是面向过程的,而c++面向对象的,为了更好使用加锁解锁。我们使用c++对互斥锁进行封装。

//Mutex.hpp/
#pragma once 
#include <iostream>
#include <pthread.h>//对锁进行封装,类似undersort_map一样先定义一个结点
//结点成员包含锁,以及加锁解锁,然后在定义了一个类
//类中成员变量是结点,然会类的加锁解锁分别调用结点的成员函数
class Mutex
{
public:Mutex(pthread_mutex_t* lock_p=nullptr):lock_p_(lock_p){}void lock(){if(lock_p_) pthread_mutex_lock(lock_p_);}void unlock(){if(lock_p_) pthread_mutex_unlock(lock_p_);}~Mutex(){}
private:pthread_mutex_t* lock_p_;
};//这才是我们最终想要的封装
class LockGuard
{
public:LockGuard(pthread_mutex_t* mutex):mutex_(mutex){mutex_.lock();//在构造函数中进行加锁}~LockGuard(){mutex_.unlock();//在析构函数中进行解锁}
private:Mutex mutex_;
};

然后我们对测试代码做一下改动

int tickets=10000;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//定义一个锁
void* getTickets(void* args)
{//加锁和解锁是多个线程串行执行的,程序变慢了。std::string username=static_cast<const char*>(args);while(true){ {//加了这个{}是相当于加了个作用域,当出了{}解锁成功,后面的usleep()代码没有加锁LockGuard lockguard(&lock);//构造并且加锁,处理作用域自带析构调用解锁函数if(tickets >0){usleep(1244);std::cout<< username<<"我正在抢票"<<tickets<< std::endl;tickets--;   }else{  break;}}//抢完票之后,我们设置一个任务。例如形成订单usleep(1000);//形成一个订单给用户}return nullptr;
}


八 可重入与线程安全 

  •  线程安全:多线程并发运行同一段代码时,并不会影响到整个进程的运行结果,就成为线程安全
  • 可重入同一个函数被不同执行流调用, 在一个执行流执行没结束时, 有其他执行流再次执行此函数, 这个现象叫 重入

1. 可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

2. 可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

九 死锁

在多把锁的场景下,我们持有自己的锁不释放,还要对方的锁,对方也是如此,此时就容易造成死锁。自己同时申请多把锁也可能造成死锁。

我们用一个例子进行说明

pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//定义一个锁
void* getTickets(void* args)
{//加锁和解锁是多个线程串行执行的,程序变慢了。std::string username=static_cast<const char*>(args);//ThreadData* td=static_cast<ThreadData*>(args);while(true){ // //加锁
//**********************************************************************pthread_mutex_lock(&lock);//在这里我们申请了两把锁pthread_mutex_lock(&lock);//在这里我们申请了两把锁
//**********************************************************************if(tickets >0){usleep(1244);std::cout<< username<<"我正在抢票"<<tickets<< std::endl;tickets--;   pthread_mutex_unlock(&lock);//解锁// usleep(1244);}else{  pthread_mutex_unlock(&lock);//解锁break;}//抢完票之后,我们设置一个任务。例如形成订单usleep(1000);//形成一个订单给用户//通过usleep(1000),线程在访问加锁的资源之后,进行休眠即阻塞状态,这个时候cpu会对其他线 程进行随机调度//从而实现了多个进程对保护的共享资源进行抢票的过程}return nullptr;
}

运行结果

🚢:为什么会造成进程卡住的情况呢?首先前面我们说明了锁的原理。 当我们申请了一把锁的时候 pthread_mutex_lock(&lock); 寄存器al 变成1,内存中mutex=0.此时al=1, 满足条件,执行后续语句,然后下一个语句又是申请一把锁  pthread_mutex_lock(&lock),前面的锁没有释放,那么后面的 pthread_mutex_lock(&lock)就会阻塞等待,当cpu切换线程执行其他线程也是会遇到这种情况,那么整个多线程就一直处于阻塞状态,从而不会执行后续cout,和tickets--等语句。导致的结果就是线程一直阻塞,显示器上什么也不显示。

1.死锁产生的必要条件

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

2.死锁的避免方法

最直接有效的避免方法是不使用锁. 虽然锁可以解决一些多线程的问题, 但是可能会造成死锁。

如果非要使用锁, 那就得考虑避免死锁,破坏死锁的四个必要条件。

相关文章:

Linux : 多线程互斥

目录 一 前言 二 线程互斥 三 Mutex互斥量 1. 定义一个锁&#xff08;造锁&#xff09; 2. 初始化锁 3. 上锁 4. 解锁 5. 摧毁锁 四 锁的使用 五 锁的宏初始化 六 锁的原理 1.如何看待锁&#xff1f; 2. 如何理解加锁和解锁的本质 七 c封装互斥锁 八 可重入…...

【数学建模】佳点集(Good Point Set)在智能优化算法中的应用与实现

佳点集(Good Point Set)在智能优化算法中的应用与实现 文章目录 佳点集(Good Point Set)在智能优化算法中的应用与实现1. 佳点集概述2. 佳点集的数学原理3. 佳点集在智能优化算法中的应用3.1 改进麻雀搜索算法(SSA)3.2 改进量子粒子群优化算法(QPSO)3.3 自适应分组差分变异狼群…...

redis linux 安装简单教程(redis 3.0.4)

redis.3.0.4.tar.gz 下载地址 链接: https://pan.baidu.com/s/19VAcrA6XS4mIesH6e5Jftg 提取码: bn2r &#xff08;1&#xff09;以安装目录&#xff1a;/home/zsl &#xff08;2&#xff09;将redis-3.0.4.tar.gz 拷贝到/home/zsl &#xff08;3&#xff09;tar xzvf redis-3.…...

探秘 Python 网络编程:构建简单聊天服务器

在计算机网络的世界里&#xff0c;网络编程是实现不同设备之间通信的关键技术。Python 凭借其简洁的语法和强大的库支持&#xff0c;在网络编程领域有着广泛的应用。无论是构建简单的聊天服务器&#xff0c;还是开发复杂的网络应用&#xff0c;Python 都能轻松胜任。 1 理论基础…...

debian转移根目录

如何在 BIOS 启动的 Debian 虚拟机中将根目录转移到 /dev/sda 设备上&#xff1f;本文将从硬盘分区&#xff0c;根目录复制&#xff0c;重新启动等几个方面介绍。 硬盘分区 1.检查磁盘&#xff1a;查看当前的磁盘和分区情况&#xff0c;确认新添加的磁盘设备名称。 parted -…...

vue3 element-plus表单验证

第一准备一个表单 form.vue <template><div><el-form><el-form-item label"姓名" prop"name"><el-input v-model"data.name" placeholder"请输入姓名"></el-input></el-form-item></e…...

Deepseek IP-Adapter与InstantID的区别

IP-Adapter与InstantID均为基于扩散模型的图像生成控制技术&#xff0c;但两者的算法设计目标、核心模块及应用场景存在显著差异。以下从技术架构、特征处理、条件控制等维度对比两者的差异&#xff1a; 1. 核心设计目标 IP-Adapter 由腾讯团队提出&#xff08;2023年8月&…...

OSI 七层模型与 TCP/IP 协议栈详解

OSI 七层模型与 TCP/IP 协议栈详解 网络协议模型是理解计算机网络和通信的基础&#xff0c;而 OSI 七层模型和 TCP/IP 协议栈是最常见的两种网络通信模型。虽然这两者有些不同&#xff0c;但它们都提供了一种分层的结构&#xff0c;帮助我们理解和设计网络通信。本文将详细介绍…...

synchronize 或者lock 锁常见的使用场景

在 Java 多线程编程中&#xff0c;synchronized 和 Lock&#xff08;如 ReentrantLock&#xff09;是两种常见的线程同步机制。以下是它们的核心区别和典型使用场景&#xff0c;结合代码示例说明&#xff1a; 一、synchronized 的常见场景 1. 简单的临界区保护 public class …...

Redis之缓存更新策略

缓存更新策略 文章目录 缓存更新策略一、策略对比二、常见的缓存更新策略三、如何选择策略四、实际应用示例五、使用 Cache-Aside TTL 的方式&#xff0c;实现缓存商铺信息详情1.引入StringRedisTemplate2.将查询商铺信息加入缓存3.更新商铺信息时移除缓存总结 六、注意事项 一…...

【操作系统学习篇-Linux】进程

1. 什么是进程 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等 内核观点&#xff1a;担当分配系统资源&#xff08;CPU时间&#xff0c;内存&#xff09;的实体。 如果你就看这个来理解进程&#xff0c;那么恭喜你&#xff0c;作为初学者&#xff0c;你…...

Docker 前瞻

一、namespace 指令 1.1 dd 命令 dd 命令用于读取、转换并输出数据。 dd 命令可从标准输入或文件中读取数据&#xff0c;根据指定的格式来转换数据&#xff0c;再输出到文件、设备或标准输出。 语法 dd option if 文件名&#xff1a;输入文件名&#xff0c;默认为标准输入…...

【maxENT】最大熵模型(Maximum Entropy Model)R语言实现

文章目录 一、相关package介绍1.1 dismo 包1.2 raster包1.3 常见问题与解决 二、代码示例 &#x1f7e2;&#x1f7e0;先看&#xff1a;【maxENT】最大熵模型&#xff08;Maximum Entropy Model&#xff09;介绍与使用&#xff08;maxENT软件&#xff09; ASCII文件太大&#…...

高负载WEB服务器--Tomcat

高负载WEB服务器–Tomcat Tomcat介绍 Tomcat 是一个开源的轻量级应用服务器&#xff0c;在 Java Web 应用开发中被广泛使用。 发展历程&#xff1a;Tomcat 最初由 Sun Microsystems 开发&#xff0c;后来成为 Apache 软件基金会的一个项目。它的发展与 Java 技术的发展密切相…...

分页查询列表每页1000条的优化

项目中有一个客户列表,要求每页显示1000条,并且字段很多,接口返回大概要10秒钟,进行优化. 原本逻辑:使用mybatisplus构建查询条件,分页查询客户表,查出数据库DO对象,然后for循环转化成回显的VO对象.在转化的过程中出现了查库代码,导致当每页条数1000时,每一个客户转化都需要查询…...

深入浅出一下Python面向对象编程的核心概念与实践应用

本篇技术博文摘要 &#x1f31f; 本文系统讲解了Python面向对象编程的核心概念与实践应用。通过电商系统用户订单模拟、动态权限账户系统等案例&#xff0c;深入剖析了类与对象、属性方法、实例方法等基础要素。重点解析了__init__构造方法、__str__对象描述、__lt__比较运算符…...

2025阿里云AI 应用-AI Agent 开发新范式-MCP最佳实践-78页.pptx

2025阿里云AI 应用-AI Agent 开发新范式-MCP最佳实践&#xff0c;包含以下内容&#xff1a; 1、AI 应用架构新范式 2、云原生API网关介绍 3、云原生API网关底座核心优势 4、流量网关最佳实践 5、AI 网关代理 LLM 最佳实践 6、MCP网关最佳实践 7、MSE Nacos MCP Server 注册中心…...

github进阶使用教程

目录索引 一、基本内容 repository fork star codespaces issue 在一个仓库创建话题讨论&#xff0c;可以由仓库主人选择开始和结束话题的讨论 pull request&#xff08;也称 pr&#xff09; 协同其他仓库开发&#xff0c;请求仓库主人拉取自己的代码合并到仓库的主分支&…...

【C++】 —— 笔试刷题day_16

刷题_day16&#xff0c;继续加油啊 一、字符串替换 题目解析 这道题是一道简单的字符题目&#xff0c;题目给我们一个字符串A&#xff0c;和n表示A字符串的长度&#xff0c;再给出一个字符数组arg&#xff0c;m表示arg中是数据个数。 然我们在字符串A中找到%s然后替换成arg中的…...

5.3 GitHub订阅系统核心架构解密:高并发设计与SQLite优化实战

GitHub Sentinel 分析报告功能实现:订阅管理核心逻辑解析 关键词:GitHub API 订阅管理, SQLite 数据库设计, RESTful API 开发, 原子操作封装, 异常处理机制 1. 订阅管理功能架构设计 订阅管理模块采用分层架构设计,通过清晰的接口隔离实现高内聚低耦合: #mermaid-svg-bW…...

P5738 【深基7.例4】歌唱比赛

P5738 【深基7.例4】歌唱比赛 题目描述 n ( n ≤ 100 ) n(n\le 100) n(n≤100) 名同学参加歌唱比赛,并接受 m ( m ≤ 20 ) m(m\le 20) m(m≤20) 名评委的评分,评分范围是 0 0 0 到 10 10 10 分。这名同学的得分就是这些评委给分中去掉一个最高分,去掉一个最低分,剩下 …...

从三次方程到复平面:复数概念的奇妙演进(三)

注&#xff1a;本文为 “复数 | 历史 / 演进” 相关文章。 因 csdn 篇幅限制分篇连载&#xff0c;此为第三篇。 生料&#xff0c;不同的文章不同的点。 机翻&#xff0c;未校。 Complex Numbers History: Complex numbers were first introduced by G. Cardano (1501-1576)…...

2025年七星棋牌跨平台完整源码解析(200+地方子游戏+APP+H5+小程序支持,附服务器镜像导入思路)

目前市面上成熟的棋牌游戏源码很多&#xff0c;但能做到平台全覆盖、地方玩法丰富、交付方式标准化的系统却不多。今天这套七星棋牌2023完整源码具备安卓/iOS/H5/微信小程序端四端互通能力&#xff0c;附带200多款地方子游戏&#xff0c;还配备了后台管理与自动热更系统&#x…...

从三次方程到复平面:复数概念的奇妙演进(四)

注&#xff1a;本文为 “复数 | 历史 / 演进” 相关文章合辑。 因 csdn 篇幅限制分篇连载&#xff0c;此为第四篇。 生料&#xff0c;不同的文章不同的点。 机翻&#xff0c;未校。 Complex number and its discovery history 复数及其发现历史 Wenhao Chen, †, Dazheng …...

UE5角色状态机中跳跃落地移动衔接问题

UE5系列文章目录 文章目录 UE5系列文章目录前言一、状态机设置二、主要蓝图 前言 先说说遇到的问题&#xff0c;在我按空格键跳跃落地以后&#xff0c;角色落地再按WSAD键移动就出现了画面中角色抽搐的情况 一、状态机设置 在Unreal Engine 5中创建角色时&#xff0c;处理跳…...

25软考中级*高项网课+历年真题+笔记+电子书+刷题【计算机软考】

两个月逆袭25年软考程序员&#xff1f;这份高效备考指南请收好 25软考中级*高项网课download &#x1f4c2; 软考中级科目备考资料介绍 ✅ 【01】2025 年软件测评师 聚焦软件测试全流程&#xff0c;涵盖 需求分析、测试设计、用例编写、缺陷管理 等核心技能。 &#x1f4d8; 备…...

C++STL——容器-list(含模拟实现,即底层原理)(含迭代器失效问题)(所有你不理解的问题,这里都有解答,最详细)

目录 1.迭代器的分类 2.list的使用 2.1 list的构造 2.2 list iterator 2.3 list capacity 2.4 list element access ​编辑 2.5 list modifiers ​编辑2.5.1 list插入和删除 2.5.2 insert /erase 2.5.3 resize/swap/clear ​编辑 2.6 list的一些其他接口…...

Linux系统编程之虚拟内存

概述 计算机内存是临时存储数据的地方&#xff0c;它比硬盘快得多&#xff0c;但容量有限。现代操作系统通过虚拟内存技术&#xff0c;使得每个进程都感觉自己独占整个地址空间&#xff0c;这不仅提高了安全性&#xff0c;也简化了内存管理。 物理内存&#xff1a;实际安装在计…...

笔试专题(八)

文章目录 平方数&#xff08;数学&#xff09;题解代码 DNA序列&#xff08;固定长度的滑动窗口&#xff09;题解代码 压缩字符串 &#xff08;双指针 模拟&#xff09;题解代码 chika和蜜柑 &#xff08;top k问题 排序 pair&#xff09;题解代码 平方数&#xff08;数学&a…...

Linux:基础IO---软硬链接动静态库前置知识

序&#xff1a;上一个章节&#xff0c;我从硬件出发&#xff0c;由宏观到微观&#xff0c;由具体到抽象&#xff0c;围绕研究对象未被打开的文件来讲解&#xff0c;操作系统是如何对一个大块的磁盘进行管理的&#xff0c;从而引进inode的概念&#xff0c;加深了对文件的理解&am…...

Arm CPU安全通告:基于TrustZone的Cortex-M系统面临多重故障注入攻击

安全之安全(security)博客目录导读 目录 一、概述 二、致谢 三、参考文献​​​​​​Black Hat USA 2022 | Briefings Schedule 四、版本历史 一、概述 Arm注意到BlackHat 2022大会官网发布的演讲摘要《糟糕..&#xff01;我又一次故障注入成功了&#xff01;——如何突…...

测试第二课-------自动化测试

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…...

深入探索Linux开发工具:Vim与Yum

目录 引言 Vim&#xff1a;强大的文本编辑利器 Vim的基本概念 Vim的基本操作 Vim正常模式命令集 Vim末行模式命令集 Vim的配置 使用插件拓展Vim功能 Yum&#xff1a;便捷的Linux软件包管理器 注意事项 结语 引言 在Linux的世界里&#xff0c;高效的开发工具是提升生…...

玩转ChatGPT:使用深入研究功能梳理思路

一、写在前面 前我尝试用ChatGPT的Deep Research&#xff08;深入研究&#xff09;功能来梳理文献&#xff0c;效果相当不错。最近&#xff0c;谷歌的Gemini 2.5 Pro也推出了类似功能&#xff0c;从网络测评来看&#xff0c;其表现与ChatGPT不相上下&#xff0c;而且还可以免费…...

UE5蓝图实现打开和关闭界面、退出

Button_Back 和Button_Exit是创建的两个按钮事件。 1.Create Widget 创建界面&#xff08;打开界面&#xff09; 2.Add to Viewport 添加到视图 3.remove form Parent&#xff0c;Target&#xff1a;self 从父节点移除当前界面&#xff08;关闭界面&#xff09; 4.Quit Game 退…...

实现vlan间的通信

这是第一种方法&#xff08;更推荐第三种&#xff09; PC1划分为vlan10&#xff0c;PC2划分为vlan20&#xff0c;实现PC1和PC2之间通信很简单&#xff0c;我们只需将网关都设置好&#xff0c;将交换机的0/0/1、0/0/3设置成vlan10&#xff0c;0/0/2、0/0/4设置成vlan20&#xf…...

Linux上位机开发实践(opencv算法硬件加速)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 图像处理里面&#xff0c;opencv基本是一个标准模块。但是由于图像处理的特点&#xff0c;如果所有的算法都是cpu来做的话&#xff0c;效率会很低。…...

智慧社区数据可视化中枢平台——Axure全场景交互式大屏解决方案

在数字化治理的时代浪潮中&#xff0c;社区管理正面临数据碎片化、响应滞后、决策盲区等核心挑战。如何将分散的安防、环境、能源、民生服务等数据整合为可操作的智慧洞察&#xff1f;如何让冰冷的数字转化为社区管理者手中的决策利器&#xff1f;Axure智慧社区可视化大屏原型模…...

动态路由, RIP路由协议,RIPv1,RIPv2

动态路由 1、回顾 路由&#xff1a;从源主机到目标主机的过程 源主机发送数据给目标主机&#xff0c;源主机会查看自身的路由信息 如果目标主机是自己同网段&#xff0c;源主机查看的是直连路由 如果目标主机和自己不同网段&#xff0c;源主机查看的是静态路由、动态路由、默…...

C++:STL的常用容器(string/vector/deque/stack/queue/list/set/multiset/map/multimap)

程序员Amin &#x1f648;作者简介&#xff1a;练习时长两年半&#xff0c;全栈up主 &#x1f649;个人主页&#xff1a;程序员Amin &#x1f64a; P   S : 点赞是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全…...

【unity游戏开发入门到精通——UGUI】Canvas画布组件

注意&#xff1a;考虑到UGUI的内容比较多&#xff0c;我将UGUI的内容分开&#xff0c;并全部整合放在【unity游戏开发——UGUI】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 一、Canvas画布组件1、Canvas组件用来干啥2、场景中可以有多个Canvas对象 二、…...

MyBatis 中 Mapper 传递参数的多种方法

# MyBatis Mapper 传递参数的多种方法及其优势 在使用 MyBatis 进行数据库操作时&#xff0c;Mapper 接口的参数传递是一个非常基础但又十分重要的部分。不同的参数传递方式适用于不同的场景&#xff0c;合理选择可以大大提高代码的可读性和维护性。本文将详细介绍几种常见的 …...

学习海康VisionMaster之平行线计算

一&#xff1a;进一步学习了 今天学习下VisionMaster中的平行线计算&#xff0c;这个是拟合直线的扩展应用&#xff0c;针对需要计算平行线的应用场合&#xff0c;可以方便的生成对应的另外一条平行线 二&#xff1a;开始学习 1&#xff1a;什么是平行线计算&#xff1f; 如果…...

MyBatis Mapper 传递参数的多种方法

1. 使用顺序传参法(不推荐) 方法描述 直接通过位置来引用参数,例如 ( arg0 arg1 … ) 或者 (param1, param2…)。 示例代码 List<User> selectUsers(String name, Integer age); <...

探索 Vue 3 响应式系统:原理与实践

Vue 3 响应式系统凭借 Proxy 的优势&#xff0c;提供更强大、灵活的响应式方案。理解其原理与 API&#xff0c;能写出更高效、可维护的 Vue 应用。不断探索其细节&#xff0c;是进阶 Vue 开发的关键。 探索 Vue 3 响应式系统&#xff1a;原理与实践 Vue 3 的响应式系统是其核…...

【LeetCode 热题100】二叉树构造题精讲:前序 + 中序建树 有序数组构造 BST(力扣105 / 108)(Go语言版)

&#x1f331; 二叉树构造题精讲&#xff1a;前序 中序建树 & 有序数组构造 BST 本文围绕二叉树的两类构造类题目展开解析&#xff1a; 从前序与中序遍历序列构造二叉树 将有序数组转换为二叉搜索树 我们将从「已知遍历构造树」和「平衡构造 BST」两个角度&#xff0c;拆…...

开源语音文本自动对齐模型:Llama-OuteTTS-1.0-1B

OuteTTS 1.0 介绍与使用指南 1. 重要采样考虑 重复惩罚机制&#xff1a;OuteTTS 1.0 要求对最近的64个token应用重复惩罚&#xff0c;而不是对整个上下文窗口。对整个上下文窗口进行惩罚会导致输出质量下降。推荐工具&#xff1a;llama.cpp 和 EXL2 提供了可靠的输出质量&…...

基于SpringBoot的电影订票系统(源码+数据库+万字文档+ppt)

504基于SpringBoot的电影订票系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户主要功能如下。 【用户功能】 首页&#xff1a;浏览系统电影动态。 资讯信息&#xff1a;获取有关电影行业的新闻和资讯。 电影信息&#xff1a;查看电影的详细信息和排片情况。 公告信…...

基于SpringBoot汽车零件商城系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...

Python数据可视化:从脚本到海报级图表

Python数据可视化:从脚本到海报级图表 引言 在数据分析和科学计算领域,Python 是一种强大且灵活的工具。本文将带您了解如何使用 Python 进行数据可视化,从简单的脚本到生成高质量的海报级图表。我们将重点介绍如何使用 Matplotlib 库来创建、保存和优化图表,以便在各种场…...