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

Linux——线程同步(条件变量、POSIX信号量)和线程池

一.线程同步

(一).概念

线程同步是一种多线程关系,指的是线程之间按照特定顺序访问临界资源,进而能够避免线程饥饿问题。

所谓线程饥饿指的是某个线程长期“霸占”临界资源,导致其他线程无法访问该资源。而通过线程同步机制能够有效避免饥饿的情况发生。

可以理解为将线程先排成一排就像队列一样,访问临界资源时按照队列顺序一个接着一个的访问。

(二).同步与互斥的关系

之前介绍过线程互斥的概念,链接在此:Linux——什么是互斥与互斥锁

当我们明白同步的概念后,需要梳理一下互斥与同步的关系。

首先,不管是线程互斥还是线程同步,指的都是线程与线程之前的一种联系。

很多文章都在说“同步是一种复杂的互斥,互斥是特殊的同步”,本人解释如下:

互斥的要求是在同一时间只能有一个线程访问临界资源,也就是线程并发执行,同步的条件是线程按次序执行,也就是说同步的条件有两个:有序和并发。同步在互斥的基础上更加强调了线程有序,因此“同步是一种复杂的互斥”。

同时,同步条件之一是有序,而互斥没有顺序要求。也就是说不管有序还是无序,只要满足同一时间只有一个线程访问临界资源就是互斥。换句话说,有序的互斥也就是同步,即“互斥是一种特殊的同步”。

总结一下,不管是互斥还是同步,共有条件是“同一时间只有一个线程访问临界资源”。因此同步与互斥都是用来处理多线程中访问临界资源问题的手段。但同步比互斥更加强调线程顺序的重要性。

(三).条件变量

使用条件变量是达成线程同步的一种手段。顾名思义,条件变量需要线程达成某种条件进而完成同步。

满足条件时线程继续运行,不满足条件时线程排队等待,直到其他线程发送特定信号,解除等待。

linux提供了条件变量的类型和接口:

头文件和互斥锁相同,为<pthread.h>,编译时需要在命令后加-pthread

g++ xxx.cpp -o xxx -pthread; //gcc同理

pthread_cond_t为条件变量类型,使用条件变量完成线程同步前必须先定义条件变量对象。

pthread_cond_t cond;

①初始化和销毁

使用条件变量必须先进行初始化操作:

linux提供了两种初始化方式,系统接口和系统宏定义。

返回值为0代表成功,非0代表失败,条件变量相关的其他接口同理。

pthread_cond_init(&cond, nullptr);//参数二为条件变量属性,默认为NULL即可
pthread_cond_init cond = PTHREAD_COND_INITALIZER;

当程序不再使用该条件变量时,请销毁它:

pthread_cond_destroy(&cond);

②阻塞等待

根据特定情况,可以使用条件变量使线程阻塞:

pthread_cond_wait(&cond, &mtx);//第二个参数为互斥锁,且必须是已经初始化以及完成加锁。

使用上通常伴随条件判断,如果满足或不满足某种条件时便会触发wait函数,进而使线程阻塞并进入等待队列,如果多个线程都触发了wait函数就会按照顺序依次进入等待队列。

示例如下:

pthread_mutex_lock(&mtx);
while(条件)
{//达成某种条件时,线程便会阻塞等待并入等待队列pthread_cond_wait(&cond, &mtx);
}
pthread_mutex_unlock(&mtx);

该接口内部,会将线程放在等待队列上并解除传递的互斥锁,这也就是为什么函数第二个参数是互斥锁mutex,等到结束阻塞的信号后,函数内部会重新争夺互斥锁,待加锁成功后退出wait函数。

图示解析如下:

③解除阻塞

当满足特定条件时,其他线程可以发送信号让等待队列中的线程停止阻塞。

pthread_cond_broadcast(&cond);//该条件变量下的所有阻塞线程继续运行
pthread_cond_signal(&cond);//该条件变量下按照等待顺序让一个阻塞线程继续运行

使用上会配合pthread_cond_wait接口使用,达到线程同步的目的。

伪代码如下:

//线程一
pthread_mutex_lock(&mtx);
while(条件)
{//尚不构成某种条件,不能获取临界资源pthread_cond_wait(&cond, &mtx);
}
...//达成条件,处理临界资源
pthread_mutex_unlock(&mtx);//线程二
...
if(条件)
{//达成某种条件,解除阻塞,让其他线程访问临界资源pthread_cond_signal(&cond);
}

尤其需要注意的是,在判断线程是否需要阻塞时,一定要用while循环判断而不是if条件判断。这是因为

线程可能因为意外情况结束阻塞,但是此时条件尚未达成,因此需要在解除阻塞后再次判断是否依旧达成条件。

(四).条件变量实际应用

就使用场景而言,一个经典的案例就是基于阻塞队列的生产消费模型。

相关代码在这篇博客中:Linux——生产消费者模型(阻塞队列形式)

这里主要解释条件变量在其中的应用,而不再具体讨论模型的实现。

当容器为空时消费者线程需要阻塞在临界区外,由于是多线程,可能会有很多线程阻塞在临界区之外。这时这些线程就需要按顺序“排队”,代码而言就是调用pthread_cond_wait接口使线程阻塞。当生产者线程将数据写入容器后,发送信号给阻塞队列,按等待队列次序让消费者线程依次运行处理临界资源。

同理当容器满时,通过另一个条件变量使生产者线程阻塞,当消费者处理数据后,发送信号给生产者使其继续运行。

需要注意的是,生产者与消费者是两个不同的条件变量。这是因为二者阻塞在不同的等待队列中,只有生产者产出数据才能解除消费者的阻塞,同时只有消费者消费数据才能解除生产者的阻塞。但双方并不会发生死锁问题,这是因为二者共用一个互斥锁,生产者与消费者是并发执行的互斥关系。

伪代码示意如下:

//生产者
void* producerFunction(void* arg)
{pthread_mutex_lock(&mtx);while(容器已满){pthread_cond_wait(&cond1, &mtx);}...//将资源放入容器的过程pthread_cond_signal(&cond2);//发送信号使消费者消费数据pthread_mutex_unlock(&mtx);...
}
//消费者
void* consumerFunction(void* arg)
{pthread_mutex_lock(&mtx);while(容器为空){pthread_cond_wait(&cond2, &mtx);}...//处理临界资源的过程pthread_cond_signal(&cond1);//发送信号使生产者生产数据pthread_mutex_unlock(&mtx);...}

二.POSIX信号量

(一).概念

与条件变量相同,信号量也是应用于线程同步的一种技术。本质上,信号量是一种资源预定机制。就好比电影院售票,票数固定,谁能买到票谁就能看电影,没有票就看不到。信号量就是电影票,提前设定好信号量的最大值,象征着有多少线程能访问临界资源,信号量--就是卖出一张票,说明有一个线程拥有了访问资源的权力;信号量++就是退掉了一张票,说明有一个线程访问过临界资源或者取消访问,同理也可以参考智能指针的引用计数。

信号量--的操作称为P操作,即线程预定了资源;信号量++的操作称为V操作,即临界资源访问完毕或取消预订。

需要注意,P操作叫做预定资源,并不是真正获取了临界资源。什么意思呢,就像我们去蜜雪冰城买奶茶,服务员给了我们一张小票,上面写着我们是第几单,前头还有多少单。此时的我们并没有拥有奶茶,而是“预定”了一杯,在未来某时就会获得奶茶。取得信号量的线程(或者叫完成P操作的线程)就是预定成功了,在未来某时就能访问临界资源。

同样的,当我们等待奶茶时,如果临时取消不想买了,把票退给服务员就是V操作中的取消预定;我们兑换小票取得奶茶就是V操作的临界资源访问完毕。V操作的两种情况就本质而言都是信号量++,只不过一个是没有访问临界资源,另一个访问完成。

(二).使用

linux提供了使用信号量的系统接口。定义在头文件<semaphore.h>中。编译时需要在命令后加上-pthread。

g++ xxx.cpp -o xxx -pthread

信号量类型为sem_t类型。

①初始化和销毁

首先需要定义sem_t类型对象并完成对信号量的初始化。

初始化的主要目的就是确定该信号量当前值。

返回值含义与条件变量接口相同,0代表成功,非0代表失败。

sem_t sem;
sem_init(&sem, 0, 5);
//参数pshared:0代表线程间共享,非0代表进程间共享
//参数value即设定的该信号量当前值,这里就代表该信号量当前还能分配给5个线程

当不再使用信号量时请及时销毁,这些和条件变量异曲同工。

sem_destroy(&sem);

②P操作(信号量--)

当线程需要访问临界资源时,请先调用sem_wait接口申请信号量,如果此时有剩余信号量那么申请成功,信号量--;如果此时信号量为0代表没有剩余信号量可以申请,此时线程阻塞在wait接口。sem_wait系统调用本身是原子性的,也就是说P操作本身是线程安全的

sem_wait(&sem);//申请失败会阻塞
sem_trywait(&sem);//申请失败不会阻塞

③V操作(信号量++)

当线程完成对临界资源的访问后,需要归还信号量便于其他线程获取访问资源的资格。也就是使信号量++。同样V操作也是原子性即线程安全的

sem_post(&sem);

在实际应用场景中,需要我们先去申请信号量(P操作),申请成功后再加锁访问临界资源,当不再访问时,解锁后归还信号量(V操作)。伪代码流程如下:

//sem、mtx必须是多线程共享都能访问的资源,也就是说这些线程看到的必须是同一份sem、mtx
//一般而言,可以把sem、mtx作为arg参数传给线程函数
void* threadFunction(void* arg)
{...sem_wait(&sem);//先申请信号量pthread_mutex_lock(&mtx);//申请信号量成功,加锁...//访问临界资源pthread_mutex_unlock(&mtx);//解锁sem_post(&sem);//归还信号量...
}

(三).实际应用:基于循环队列的生产消费模型

在上文中我们提到了基于阻塞队列形式的生产消费模型,那里是使用条件变量来完成生产者与消费者的同步过程。而生产消费模型还可以是循环队列的形式,使用信号量来完成线程同步的过程。

首先简单说一下什么是循环队列,本质就是长度固定的数组,从头开始插入资源,当插入资源位于最后一个位置的下一个时,再从头开始插入资源,也就是把线性的数组“头尾相连”,变成逻辑上的环形结构。不再具体解释,讲解循环队列的文章网上很多。这里重点说明信号量在生产消费模型中是怎么使用的。

图示如下:

首先我们需要知道信号量作为一种预定机制,要信号量清楚在生产消费模型中是在预定什么,这一点非常重要!

对于生产者是预定队列位置,确保有位置能供自己放入资源数据。对于消费者是预定容器(循环队列)资源,确保容器中的现有资源有自己一份。生活中的例子比比皆是,比如蛋糕店生产蛋糕就要确保柜台上还有位置能放蛋糕,而顾客要看柜台上是否还有蛋糕确定能不能买到。

想清楚这一点就不难发现,生产者和消费者是预定了两种不同的资源:空位和容器剩余资源。因此,我们要定义两个信号量来代表这两种资源。并且在初始化时空位数量要为队列长度,剩余资源数量为0。

sem_t placeSem;//生产者预定的空位
sem_t dataSem;//消费者预定的容器剩余资源
sem_init(&placeSem, 0, 队列长度);
sem_init(&dataSem, 0, 0);

在使用时,生产者先预定空位(placeSem进行P操作),然后加锁把数据放入队列再解锁,最后使容器剩余资源数量+1(dataSem进行V操作)。

伪代码如下:

//生产者
void* producerFunction(void* arg)
{sem_wait(&placeSem);//先预定空位pthread_mutex_lock(&prod);//生产者与生产者是互斥关系,加生产者间的互斥锁...//资源放入容器中pthread_mutex_unlock(&prod);//解锁sem_post(&dataSem);//容器剩余资源数量+1...
}

需要说明的是,加锁的过程可以在P操作(预定空位)之前,但是没有必要。因为加锁后的线程一定能访问临界资源,也就是说加锁后的线程肯定是预定了空位的。进行信号量P操作的目的是为了消费者与生产者线程同步,且锁加在P操作之后还能降低粒度。

可能还会有疑问,为什么最后不归还空位使placeSem+1呢,请先看消费者的处理过程,之后会进行说明。

对于消费者而言,先预定容器剩余资源(dataSem进行P操作),然后加锁获取容器数据再解锁,最后使容器空位+1(placeSem进行V操作)。

伪代码如下:

//消费者
void* consumerFunction(void* arg)
{sem_wait(&dataSem);//预定剩余资源pthread_mutex_lock(&consum);//消费者之间是互斥关系,加消费者间的互斥锁...//获取容器资源pthread_mutex_unlock(&consum);//解锁sem_post(&placeSem);//容器空位+1...
}

这时我们会有两个问题:一是为什么生产之后不立即释放预定的空位(placeSem的V操作),二是为什么生产者和消费者的互斥锁不是同一个

回答第一个问题,首先就逻辑上而言,当完成生产时,我们预定的那个空位已经被生产的数据所填满,此时那个空位已经不再存在。其次,就资源层面来讲,当生产者生产数据放入空位后,如果此时空位数量还是生产前那么多,未免太不合理了吧。因此,只有在消费者消费数据后才能空位+1,也就是在消费者函数中进行placeSem的V操作。同理,只有生产者生产数据后代表剩余资源的dataSem信号量才能进行V操作。

回答第二个问题,首先我们知道在阻塞队列形式的生产消费模型中生产者与消费者的互斥锁是同一个,这是因为生产者与消费者是互斥关系,本质原因是防止生产者与消费者访问同一个资源。但是,在阻塞队列形式中,虽然生产者与消费者共享同一个容器,但是容器内部会定义两个变量,分别代表生产者与消费者各自访问的资源在队列中的下标。而基于信号量的特性,当队列为空时,消费者一定会阻塞在申请剩余资源的P操作那里,只有当生产者生产数据后使剩余资源+1,消费者才能进入临界区获取资源。也就是说,消费者永远在生产者的“屁股后头”,那么双方访问的下标永远不会相同。因此生产者和消费者只需要各自加锁。

从生产消费模型的两种形式能看出,信号量和条件变量都是用来完成线程同步的工具,但是条件变量可以一次性唤醒所有线程,而信号量不行。同样地,信号量能够根据此时的计数值记录状态,而条件变量不行。并且信号量的一大使用特色是作为进程间同步的工具,而条件变量是作为线程间同步的工具。

三.综合应用:线程池

(一).自制线程池

线程池是利用池化技术维护多个线程,当需要处理任务时便调度维护的线程,这样不仅可以保证对内核的充分利用,还可以避免过分调度。主要的应用场景是任务处理过程短且需要大量线程的环境。比如Web服务器完成网页请求就属于这类情况,有大量的网页点击需求,任务小但是需求大,使用线程池能够避免大量的线程创建的等待时间。而会话请求就不太合适,因为会话时间相比于创建线程要长很多,线程池的优点就不太明显。此外,如果是要求迅速响应的任务和瞬间需求大量线程的应用(因为瞬间创建大量线程可能导致内存极限进而出错),线程池技术都比较合适。

概念上,线程池本身也是一种生产消费模型。生产过程就是获取任务到线程池中,消费过程就是调度具体的线程处理任务。因此,我们可以提前创建多个线程作为消费者,使用循环队列作为存储任务的容器。当容器中有任务时,按照次序调度线程处理任务。

源码:threadPool/threadPoolPlus · 纽盖特/linux - 码云 - 开源中国 (gitee.com)

伪代码如下:

/*
优化线程池:
将队列划分为生产消费两个
生产者生产数据后,当数据满足一定数量时交换生产消费队列
同时能使生产与消费互斥关系降到最低,只有在交换队列时才会互斥
*/
struct ThreadData{//线程数据...//线程名、线程id等数据void* _arg;//记录该线程的线程池指针,因为线程池_threadFunc函数为静态,无法直接使用threadPool对象资源和成员函数
};
class Thread{//线程类
public:Thread(.../*其他线程数据*/, tFunc func, void* arg = nullptr):_func(func){...//记录其他线程数据_data._arg = arg;}void run()//启动线程,函数传参{pthread_create(&_data._tid, nullptr, _func, (void*)&_data);}void join(){pthread_join(_data._tid, nullptr);}private:ThreadData _data;tFunc _func;//线程调度的函数
};#define THREAD_NUM 5//消费者线程数量
#define QUEUE_MAX_SIZE 5//默认容器大小
template<class T>
class ThreadPool{static void* _threadFunc(void* arg)//线程执行任务的函数{//通过参数,获取threadPool对象,因为该函数是静态,没有this指针ThreadData* data = (ThreadData*)arg;ThreadPool<T>* pool = (ThreadPool<T>*)data->_arg;while(true)//某线程循环等待处理队列任务{T task;{...//获取消费者锁while(pool->isEmpty()){...//当队列空时,阻塞等待生产者交换队列}task = pool->getTask();//获取任务...//解除消费者锁}task();//执行资源内容}return nullptr;}void swapQueue(){...//交换生产者消费者队列}
public:bool isFull()//判断队列是否已满{return _quP->size() == QUEUE_MAX_SIZE;}bool isEmpty()//判断队列是否已空{return _quC->size() == 0;}T getTask()//从阻塞队列中获取任务{T task = _quC->front();_quC->pop();return task;}
public:ThreadPool(size_t num = THREAD_NUM)//参数:定义线程池线程数量:_num(num){for(size_t i=1; i<=_num; i++)//创建线程{...//记录线程名,编号//传递this作为线程函数参数,是因为_threadFunc为static,无法看见阻塞队列//传递this指针给线程,调度函数时能获取threadPool对象_threads[i - 1] = new Thread(线程名, 编号, _threadFunc, this);}pthread_mutex_init(&_consum, nullptr);pthread_mutex_init(&_prod, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){for(size_t i=0; i<_num; i++)//销毁线程{_threads[i]->join();delete _threads[i];}pthread_mutex_destroy(&_consum);pthread_mutex_destroy(&_prod);pthread_cond_destroy(&_cond);}void pushTask(const T& task)//获取任务至阻塞队列中{{...//加锁,将任务写入生产队列中,解锁}while(isFull()){...//当任务满时,获取消费锁,交换队列pthread_cond_signal(&_cond);//向消费者发送信号..//解锁}}void start()//启动线程{for(size_t i = 0; i < _num; i++){_threads[i]->run();//启动线程}}pthread_mutex_t _consum;pthread_mutex_t _prod;pthread_cond_t _cond;
private:queue<T>* _quC = new queue<T>();//消费者队列queue<T>* _quP = new queue<T>();//生产者队列size_t _num;//线程数量Thread* _threads[THREAD_NUM];//线程组
};

(二).拓展学习:thrmgr线程池

参考源码:linux线程池thrmgr源码解析 - 一字千金 - 博客园 (cnblogs.com)

相比于我们自制的线程池,thrmgr提供了主动销毁线程池的函数,并能防止出现某些线程长期得不到调度的情况。当然thrmgr相比于我们自制的线程池肯定还有不少丰富,这里我们重点谈论这两个优势。

首先,自制的线程池采用析构函数销毁线程,也就是RAII技术。但是thrmgr线程池提供了thrmgr_destroy函数用来销毁线程。当然这也是因为thrmgr并不是将调度函数封装在线程池类中,而是像malloc和free一样利用函数接口的形式调用线程池。线程池结构体中记录了当前还有多少线程“存活”,主线程调用thrmgr_destroy函数后,首先向所有线程发送信号让其结束,然后主线程阻塞在当前位置。当最后一个线程结束时会向主线程发送信号停止阻塞,然后完成线程池资源的释放。

thrmgr防止线程长期得不到调度的方式也很简单,当线程没有任务时,采用pthread_cond_timedwait函数阻塞等待,该函数第三个参数为设定的timespec时间类型结构体,当检测超时后自动停止阻塞并返回特定值,根据返回值就能判断线程是否是因为超时而停止阻塞,一旦判断超时,线程跳出等待任务的循环然后结束本线程。

四.其他线程安全问题

(一).单例模式

单例模式分为懒汉和饿汉两种形式。懒汉是在使用单例是才分配空间,饿汉是在程序加载时(main函数启动之前)就分配对空间。当使用饿汉模式时因为是在main函数之前,此时只有一个线程,因此不会存在线程安全问题。但是懒汉模式下,如果是在线程调度的函数中才第一次使用单例,那么就有可能有多个线程给单例分配空间。也就是说分配的空间就是一种临界资源。因此,当使用懒汉模式时,需要在分配空间时加锁

template <typename T>
class Singleton {
public:static T* GetInstance() {if (inst == nullptr) { lock.lock(); //加锁保护if (inst == nullptr) {//再次判断是否为空,因为分配空间后其他线程可能取得锁进入临界区inst = new T();}lock.unlock();}return inst;}
private:...//禁用拷贝构造等操作volatile static T* inst; // 设置 volatile 关键字防止编译器优化.static std::mutex lock;//互斥锁,确保分配空间的原子性
};

(二).STL与智能指针

STL并不是线程安全的,因此需要使用者自己维护。

对于智能指针而言,unique_ptr因为只能有一个使用者,因此不用考虑线程安全问题。shared_ptr内部在改变引用计数时标准库将其实现为原子性操作,因此shared_ptr内部也是线程安全的。但是指针引用的空间在使用时并不是线程安全,因此当使用shared_ptr时建议把它按照临界资源考虑,正常上锁。

(三).读写者问题

在实际开发中,可能会有资源需要经常访问但是很少修改的情况,比如网络小说就是会有大量读者频繁,但是作者只有一位且对比阅读来讲修改次数极少。这时如果采用生产消费模型就不太合适了。此时的读者并不会修改数据,也就是说消费者之间并没有互斥关系。因此,不需要在消费者之间加互斥锁,进而支持多线程同时访问临界资源,提高程序效率。这就是读写者模型。同时默认情况下,读者的优先级高于写者,换句话说当读者线程与写者线程同时访问临界资源时,会阻塞写者直到所有读者访问完毕。linux系统提供了读写锁和对应系统调用接口,使用方式如下:

pthread_rwlock_t rwlock;//定义读写锁
pthread_rwlockattr_t attr;//定义读写锁属性(读写者优先级)
//自定义读写优先级,默认读者优先级高
int pthread_rwlockattr_setkind_np(&attr, int pref);
/*
pref 共有 3 种选择:
PTHREAD_RWLOCK_PREFER_READER_NP 默认读者优先,可能会导致写者饥饿
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有可能与默认情况一致的BUG
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/
//初始化
int pthread_rwlock_init(&rwlock, &attr);
//销毁
int pthread_rwlock_destroy(&rwlock);
//加锁和解锁
int pthread_rwlock_rdlock(&rwlock);//读者加锁
int pthread_rwlock_wrlock(&rwlock);//写者加锁
int pthread_rwlock_unlock(&rwlock);//解锁

简单是稳定的前提。— Edsger Dijkstra


如有错误,敬请斧正

相关文章:

Linux——线程同步(条件变量、POSIX信号量)和线程池

一.线程同步&#xff08;一&#xff09;.概念线程同步是一种多线程关系&#xff0c;指的是线程之间按照特定顺序访问临界资源&#xff0c;进而能够避免线程饥饿问题。所谓线程饥饿指的是某个线程长期“霸占”临界资源&#xff0c;导致其他线程无法访问该资源。而通过线程同步机…...

leaflet 上传CSV文件,导出geojson格式文件(064)

第064个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载CSV文件,将图形显示在地图上。点击导出geojson,下载成geojson文件。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共114行)安装插件…...

Java内部类

文章目录一、内部类的概念二、内部类的分析三、内部类的分类1. 成员内部类2. 静态内部类3. 局部内部类4. 匿名内部类匿名内部类与Lambda表达式一、内部类的概念 在 Java 中&#xff0c;可以将一个类定义在另一个类里面或者一个方法里面&#xff0c;这样的类称为内部类。内部类…...

Centos系统里运行java的jar包

目前使用springboot开发是嵌入方式的tomcat&#xff0c;不需要单独使用tomcat&#xff0c;那么经常在服务器上运行jar包&#xff0c;这里记录一下在centos7系统里运行jar的方式。在运行之前需要确定centos7系统是否安装了java环境以及配置环境变量&#xff0c;还有jar需要运行的…...

招标采购流程的电子招标采购,是管理复杂供应链和多层供应商的高效方式。

负载均衡&#xff08;Load Balance&#xff09; 由于目前现有网络的各个核心部分随着业务量的提高&#xff0c;访问量和数据流量的快速增长&#xff0c;其处理能力和计算强度也相应地增大&#xff0c;使得单一的服务器设备根本无法承担。在此情况下&#xff0c;如果扔掉现有设…...

【C语言】C程序结构和基本语法

1、C语言程序结构 我们学习一门编程语言&#xff0c;第一个实例都是"hello world!"&#xff0c;下面看一个最简单的C程序结构。 #include <stdio.h>int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }程序的第一行 #incl…...

YOLOv8 目标检测 | 自定义数据集

本文介绍了使用用于目标检测的自定义数据训练 YOLOv8 模型。我正在使用来自 kaggle 的 yolo 格式的“Face Mask Dataset”&#xff0c;数据集链接如下&#xff1a;https://www.kaggle.com/datasets/maalialharbi/face-mask-dataset?resourcedownloadYOLOv8 是目前最先进的 YOL…...

Lua语法入门

注意&#xff1a;文章将持续更新完善 文章目录一. 初识Lua二. HelloWorld三. Lua的数据类型四. 变量五. 循环六. 函数七. 条件控制一. 初识Lua Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#…...

华为OD机试真题JAVA实现【最小步骤数】真题+解题思路+代码(20222023)

🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出示例一输入输出说明示例二输入输出解题思路...

预检请求OPTIONS

这里写目录标题简单请求和非简单请求简单请求非简单请求预检请求OPTIONS简单请求和非简单请求 浏览器将请求分为两大类&#xff1a;简单请求&#xff08;simple request&#xff09;和非简单请求&#xff08;not-so-simple request&#xff09; 简单请求 简单请求&#xff0…...

引入短信服务发送手机验证码进行安全校验

其他方案>引入QQ邮箱发送验证码进行安全校验 相对短信验证码&#xff0c;操作更简单而且免费 最近想给自己的项目在注册时加点安全校验&#xff0c;准备使用免费的邮箱验证来着&#xff0c;在上一篇引入QQ邮箱进行安全校验时&#xff0c;看有朋友说阿里云会送一些短信服务免…...

opencv绘制直线

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

Seata源码学习(五)- Seata服务端(TC)源码解读

Seata源码分析- Seata服务端&#xff08;TC&#xff09;源码解读 上节课我们已经分析到了SQL语句最终的执行器&#xff0c;但是再往下分析之前&#xff0c;我们需要先来分析一下TM客户端与TC端通讯以后&#xff0c;TC端的具体操作 服务端表解释 我们的Seata服务端在应用的时…...

低版本jQuery导致XSS Nuclei FUZZ POC

目录 1.前言 2. Nuclei FUZZ jQuery XSS POC 3.漏洞验证 4.修复建议 1.前言 我记得以前用那些漏扫工具时时常会报一个低版本jQuery的安全问题,当时还不会验证。直到有一天,它托梦给我。我悟了。低版本jQuery导致XSS POC文件文末获取。...

【Linux】进程的描述组织与进程状态

文章目录&#x1f3aa; 进程的描述组织&#x1f680;1.什么是进程&#x1f680;2.进程的形成&#x1f680;3.进程标识符 *⭐3.1 PS命令查看PID⭐3.2 /proc目录查看进程属性&#x1f680;4.父子进程⭐4.1 系统调用获取PID⭐4.2 fork创建子进程⭐4.3 fork双返回值问题⭐4.4 写时拷…...

8.2.1.1 WHERE 子句优化

本节讨论可用于处理 WHERE 子句的优化。示例使用 SELECT 语句&#xff0c;但相同的优化适用于 DELETE 和 UPDATE 语句中的 WHERE 子句。 注意 因为 MySQL 优化器的工作正在进行&#xff0c;所以这里并没有记录 MySQL 执行的所有优化。 您可能会尝试重写查询以使算术运算更快&am…...

拆个微波炉,分析一下电路

微波炉是用2450MHz的超高频电磁波来加热食品&#xff0c;它能无损穿越塑料&#xff0c;陶瓷&#xff0c;不能穿越金属&#xff0c;碰到金属会反射&#xff0c;但穿过含水食物&#xff0c;食物内的分子会高速摩擦&#xff0c;产生热量&#xff0c;使食物变熟。在厨房电器中&…...

DM8:DMDSC共享存储集群搭建-共享存储绑定

DM8:DMDSC共享存储集群搭建-共享存储绑定环境介绍&#xff1a;1 发现共享磁盘2 对共享存储进行分区格式化2.1 格式化成功但不可用2.2 解决问题修改错误的分区格式3 配置/etc/rc.d/rc.local3.1 编辑文件&#xff08;两个节点配置相同&#xff09;3.2 使rc.local生效4 重启操作系…...

Spark OOM问题常见解决方式

文章目录Spark OOM问题常见解决方式1.map过程产生大量对象导致内存溢出2.数据不平衡导致内存溢出3.coalesce调用导致内存溢出4.shuffle后内存溢出5. standalone模式下资源分配不均匀导致内存溢出6.在RDD中&#xff0c;共用对象能够减少OOM的情况优化1.使用mapPartitions代替大部…...

【Calcite源码学习】ImmutableBitSet介绍

Calcite中实现了一个ImmutableBitSet类&#xff0c;用于保存bit集合。在很多优化规则和物化视图相关的类中都使用了ImmutableBitSet来保存group by字段或者聚合函数参数字段对应的index&#xff0c;例如&#xff1a; //MaterializedViewAggregateRule#compensateViewPartial()…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...