从零开始学习Linux(13)---多线程

目录

1.线程

1.线程的概念

2.线程的理解(Linux系统为例)---一般系统

3.进程vs线程

4.线程的控制

5.线程的等待

6.线程的终止

7.线程的分离

2.线程的互斥

1.互斥锁

2.条件变量

3.生产消费模型

4.阻塞队列

5.信号量

6.唤醒队列


1.线程

1.线程的概念

        线程是进程内部的一个执行分支,线程是CPU调度的基本单位。

        加载到内存中的程序,叫做进程。修正=内核数据结构+进程代码和数据。

2.线程的理解(Linux系统为例)---一般系统

        地址空间是计算机内存管理中的一个基本概念,它指的是一个程序在运行时可以访问的内存地址的集合。地址空间为程序提供了一个抽象层,使得程序可以独立于其他程序和物理内存的实际情况来访问内存。

  • 物理地址空间:指的是实际的物理内存地址,即内存条上的每一个存储单元都有一个唯一的物理地址。物理地址空间由硬件直接管理。

  • 虚拟地址空间:是相对于物理地址空间的一个抽象层,它由操作系统创建和管理。程序通常使用虚拟地址来访问内存,然后由操作系统和硬件将这些虚拟地址映射到物理地址。

        虚拟地址到物理地址的映射通常通过页表来实现。当程序访问一个虚拟地址时,内存管理单元会查找页表,找到对应的物理地址,然后进行实际的内存访问。

        地址空间和地址空间上的虚拟地址,本质是一种资源。

3.进程vs线程

        进程是操作系统进行资源分配和调度的基本单位。每个进程都拥有独立的地址空间、执行堆栈、程序计数器、寄存器集合以及系统资源。

        线程是进程内的一个执行流,是CPU调度和分派的基本单位。同一进程中的线程共享地址空间和其他资源。

4.线程的控制

        pthread_create是 POSIX 线程(pthread)库中的一个函数,用于在 POSIX 兼容的操作系统中创建新的线程。以下是关于pthread_create 的详细信息:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • thread:这是一个输出参数,用于存储新创建线程的线程 ID。
  • attr:这是一个输入参数,用于设置新线程的属性。如果传递NULL,则新线程将使用默认属性。
  • start_routine:这是一个函数指针,指向新线程将要执行的函数。该函数接受一个void*类型的参数,并返回一个 void* 类型的值。
  • arg:这是传递给start_routine函数的参数。
  • 成功:返回 0。
  • 失败:返回错误编号,可以通过pthread_error获取。
#include<iostream>      // 包含标准输入输出流库
#include<pthread.h>     // 包含 POSIX 线程库
#include<unistd.h>      // 包含 UNIX 标准函数库,如 sleep()using namespace std;   // 使用标准命名空间// 线程函数
void *newthreadrun(void *args) {while (true) {    // 无限循环// 输出新线程的信息和进程 IDcout << "I am new thread, pid:" << getpid() << endl;sleep(1);     // 休眠 1 秒}// 这个函数不会退出,因此不需要 return 语句
}int main() {pthread_t tid;    // 声明线程 ID 变量// 创建新线程,传入线程函数和参数pthread_create(&tid, nullptr, newthreadrun, nullptr);while (true) {    // 无限循环// 输出主线程的信息和进程 IDcout << "I am main thread, pid:" << getpid() << endl;sleep(1);     // 休眠 1 秒}// 主循环也不会退出,因此不会执行到这里// 如果需要正常退出程序,应该添加适当的退出条件或信号处理
}

5.线程的等待

        pthread_join是 POSIX 线程(pthread)库中的一个函数,用于等待线程结束并获取其返回值。以下是关于pthread_join的详细信息:

int pthread_join(pthread_t thread, void **retval);
  • thread:这是要等待的线程的线程 ID。
  • retval:这是一个输出参数,用于存储线程结束时返回的值。如果线程没有返回值,则将其设置为NULL。
  • 成功:返回 0。
  • 失败:返回错误编号,可以通过pthread_error获取。
#include<iostream>
#include<string>
#include<pthread.h>
#include<unistd.h>
//同一个进程内的线程,大部分资源都是共享的,地址空间是共享的
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id,sizeof(id),"0x%lx",tid);return id;
}using namespace std; void *threadrun(void *args)
{string threadname=(char*)args;int cnt=5; while(cnt){cout<<threadname<<"is running:"<<cnt<<",pid:"<<getpid()<<"mythread id:"<<ToHex(pthread_self())<<endl;sleep(1);cnt--;}return (void*)123;
}
//主线程退出==进程退出==所以线程都要退出
//1.往往我们需要main thread最后结束
//2.线程也要被"wait",不然可能内存泄漏
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");void* ret=nullptr;int n=pthread_join(tid,&ret);cout<<"main thread quit,n="<<n<<"main thread get a ret:"<<(int)ret<<endl;sleep(5);// int cnt=10;// while(cnt)// {//     cout<<"main thread is running:"<<cnt<<",pid:"//     <<getpid()<<"new thread id:"<<ToHex(tid)<<" "//     <<"main thread id:"<<ToHex(pthread_self())<<endl;//     sleep(1);//     cnt--;// }return 0;
}

6.线程的终止

        pthread_exit是 POSIX 线程(pthread)库中的一个函数,用于使当前线程立即终止并返回指定的值。以下是关于pthread_exit的详细信息:

void pthread_exit(void *retval);

        retval:这是线程退出时返回的值。这个值会被传递给调用pthread_join的线程

        pthread_cancel是 POSIX 线程(pthread)库中的一个函数,用于请求取消另一个线程。当一个线程被取消时,它会收到一个取消请求,并可以选择如何响应这个请求。以下是关于pthread_cancel的详细信息:

int pthread_cancel(pthread_t thread);

        thread:这是要取消的线程的线程 ID。

const int threadnum = 5; // 定义线程数量为5// Task类,表示一个任务
class Task {
public:Task() {} // 构造函数void SetData(int x, int y) { // 设置任务数据datax = x;datay = y;}int Excute() { // 执行任务,返回数据x和数据y的和return datax + datay;}~Task() {} // 析构函数
private:int datax; // 任务数据xint datay; // 任务数据y
};// ThreadData类,继承自Task类,用于包装任务和线程名称
class ThreadData : public Task {
public:ThreadData(int x, int y, const string &threadname): _threadname(threadname) { // 构造函数,设置线程名称_t.SetData(x, y); // 设置任务数据}string threadname() { // 返回线程名称return _threadname;}int run() { // 执行任务return _t.Excute();}
private:string _threadname; // 线程名称Task _t; // 任务数据
};// Result类,用于存储线程执行结果
class Result {
public:Result() {} // 构造函数~Result() {} // 析构函数void SetResult(int result, const string &threadname) { // 设置结果和线程名称_result = result;_threadname = threadname;}void Print() { // 打印结果cout << _threadname << ":" << _result << endl;}
private:int _result; // 结果string _threadname; // 线程名称
};void *handlerTask(void *args) {ThreadData *td = static_cast<ThreadData*>(args); // 获取线程数据string name = td->threadname(); // 获取线程名称Result* res = new Result(); // 创建结果对象int result = td->run(); // 执行任务res->SetResult(result, name); // 设置结果和线程名称cout << name << "run result:" << result << endl; // 打印结果delete td; // 删除线程数据sleep(2); // 线程休眠2秒return res; // 返回结果对象
}int main() {vector<pthread_t> threads; // 线程ID向量for (int i = 0; i < threadnum; i++) { // 创建线程char threadname[64]; // 线程名称缓冲区snprintf(threadname, 64, "Thread-%d", i + 1); // 格式化线程名称ThreadData *td = new ThreadData(10, 20, threadname); // 创建线程数据pthread_t tid; // 线程IDpthread_create(&tid, nullptr, handlerTask, td); // 创建线程threads.push_back(tid); // 添加线程ID到向量}vector<Result*> result_set; // 结果集合void *ret = nullptr;for (auto& tid : threads) { // 等待每个线程完成pthread_join(tid, &ret); // 等待线程完成Result* res = static_cast<Result*>(ret); // 获取返回的结果对象result_set.push_back(res); // 添加结果对象到集合}for (auto& res : result_set) { // 打印每个线程的结果res->Print(); // 打印结果delete res; // 删除结果对象}
}

7.线程的分离

        在Linux环境中,pthread_detach函数用于设置线程的分离属性。分离线程是指线程在完成其工作后立即退出,而不等待其他线程来收集它的返回值或清理它的资源。以下是关于pthread_detach的详细信息:

int pthread_detach(pthread_t thread);

        当一个线程被设置为分离状态并完成其工作后,它会立即退出,并且不会返回任何值给调用pthread_join的线程。这意味着调用pthread_join的线程不会接收到任何值,也不会被阻塞。

2.线程的同步和互斥

        线程互斥是指操作系统提供的一种机制,用于确保多个线程在访问共享资源时能够互不干扰,从而避免数据竞争和不一致性。线程互斥通常通过以下几种方式实现:

下面是封装了一个简单的线程:

#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{// 定义了一个模板函数类型,用于线程执行函数template<typename T>using func_t = std::function<void(T&)>; // 使用引用传递,允许修改传入的数据// 线程类模板template<typename T>class Thread{public:// 执行传入的函数对象void Excute(){_func(_data);}public:// 构造函数,初始化线程所需的数据和函数Thread(func_t<T> func, T &data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){}// 静态成员函数,作为线程的执行函数static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!{Thread<T> *self = static_cast<Thread<T> *>(args); // 将void*指针转换为Thread<T>指针self->Excute(); // 调用Excute函数执行传入的函数对象return nullptr;}// 启动线程bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this); // 创建线程if(!n) // 如果创建成功{_stop = false; // 设置停止标志为falsereturn true;}else{return false;}}// 分离线程,线程结束后资源会被自动回收void Detach(){if(!_stop) // 如果线程未停止{pthread_detach(_tid); // 分离线程}}// 等待线程结束void Join(){if(!_stop) // 如果线程未停止{pthread_join(_tid, nullptr); // 等待线程结束}}// 获取线程名称std::string name(){return _threadname;}// 设置线程停止标志void Stop(){_stop = true;}// 析构函数~Thread() {}private:pthread_t _tid; // 线程IDstd::string _threadname; // 线程名称T &_data;  // 引用传递的数据,所有线程可以访问同一个数据func_t<T> _func; // 函数对象,线程执行的函数bool _stop; // 线程停止标志};
} // namespace ThreadModule#endif

1.互斥锁

        互斥锁(Mutex)是一种同步机制,用于保护共享资源,防止多个线程同时访问。以下是关于互斥锁的详细信息:

  • 程序在访问共享资源之前,必须先获得互斥锁。
  • 只有当线程持有互斥锁时,它才能访问共享资源。
  • 线程在完成对共享资源的访问后,必须释放互斥锁,以便其他线程可以获取它。
  • 互斥锁通常用于保护临界区,即那些只允许一个线程访问的代码段。
  • 在多线程编程中,互斥锁是确保数据一致性和防止竞态条件的关键。

临界区的主要特点是:

  1. 共享资源:临界区访问的是共享资源,即多个线程可以访问的资源。

  2. 互斥访问:为了避免竞态条件和其他同步问题,临界区内的代码必须保证在同一时刻只有一个线程可以执行。

  3. 执行时间:临界区的执行时间应该尽可能短,以减少线程的阻塞和等待时间。

        pthread_mutex_lock是 POSIX 线程(pthread)库中的一个函数,用于获取互斥锁。当一个线程尝试访问一个共享资源时,它必须首先获取互斥锁。以下是关于pthread_mutex_lock的详细信息:

int pthread_mutex_lock(pthread_mutex_t *mutex);

mutex这是指向pthread_mutex_t类型的指针,用于指定要获取的互斥锁。

        pthread_mutex_unlock是 POSIX 线程(pthread)库中的一个函数,用于释放互斥锁。当一个线程完成对共享资源的访问后,它必须释放互斥锁,以便其他线程可以获取它。以下是关于pthread_mutex_unlock的详细信息:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

2.条件变量

        条件变量(Condition Variable)是操作系统提供的一种同步机制,用于线程间的通信。它通常与互斥锁一起使用,以确保线程在等待某些条件满足时不会竞争共享资源。以下是关于条件变量的详细信息:

  • 当线程需要等待某些条件满足时,它会释放当前持有的互斥锁,然后等待条件变量。
  • 当其他线程满足这些条件时,它会通知等待的线程,然后等待的线程会重新获取互斥锁并继续执行。

        pthread_cond_init是 POSIX 线程(pthread)库中的一个函数,用于初始化一个条件变量。条件变量是线程间通信的同步机制,它允许线程在等待某些条件满足时不会竞争共享资源。

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr);
  • cond:这是指向pthread_cond_t类型的指针,用于指定要初始化的条件变量。
  • cond_attr:这是一个可选参数,指向pthread_condattr_t类型的指针,用于指定条件变量的属性。如果传递NULL,则使用默认属性。

        pthread_cond_destory是 POSIX 线程(pthread)库中的一个函数,用于清理条件变量。当一个条件变量不再需要时,可以使用pthread_cond_destory函数来释放它所占用的资源。

int pthread_cond_destroy(pthread_cond_t *cond);

        pthread_cond_wait是 POSIX 线程(pthread)库中的一个函数,用于线程在等待条件变量时释放互斥锁。当一个线程需要等待某个条件满足时,它会释放当前持有的互斥锁,然后等待条件变量。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • cond:这是指向pthread_cond_t类型的指针,用于指定要等待的条件变量。
  • mutex:这是指向pthread_mutex_t类型的指针,用于指定当前持有的互斥锁。

        pthread_cond_signal是 POSIX 线程(pthread)库中的一个函数,用于唤醒一个等待条件变量的线程。当一个线程满足某些条件时,它可以使用pthread_cond_signa函数来通知其他等待的线程。

int pthread_cond_signal(pthread_cond_t *cond);
  • cond:这是指向pthread_cond_t类型的指针,用于指定要发送信号的条件变量。

        pthread_cond_broadcast是 POSIX 线程(pthread)库中的一个函数,用于唤醒所有等待特定条件变量的线程。当一个线程满足某些条件时,它可以使用pthread_cond_broadcast函数来通知所有等待的线程。

int pthread_cond_broadcast(pthread_cond_t *cond);

下面是一段测试代码:

#include<iostream>
#include<string>
#include<pthread.h>
#include<vector>
#include<unistd.h>pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;void *SlaverCore(void *args)
{std::string name=static_cast<const char*>(args);while(true){pthread_mutex_lock(&gmutex);pthread_cond_wait(&gcond,&gmutex);std::cout<<"当前被叫醒的线程是:"<<name<<std::endl;pthread_mutex_unlock(&gmutex);}
}void *MasterCore(void *args)
{sleep(3);std::cout<<"master开始工作..."<<std::endl;std::string name=static_cast<const char*>(args);while(true){pthread_cond_signal(&gcond);std::cout<<"master唤醒一个线程..."<<std::endl;sleep(1);}
}void StartMaster(std::vector<pthread_t> *tidsptr)
{pthread_t tid;int n=pthread_create(&tid,nullptr,MasterCore,(void*)"Master Thread");if(n==0){std::cout<<"create success"<<std::endl;}tidsptr->emplace_back(tid);
}void StartSlaver(std::vector<pthread_t> *tidsptr,int threadnum=3)
{for(int i=0;i<threadnum;i++){char *name=new char[64];snprintf(name,64,"slaver-%d",i+1);pthread_t tid;int n=pthread_create(&tid,nullptr,SlaverCore,name);if(n==0){std::cout<<"create success:"<<name<<std::endl;tidsptr->emplace_back(tid);}}
}void WaitThread(std::vector<pthread_t> &tids)
{for(auto &tid:tids){pthread_join(tid,nullptr);}
}int main()
{std::vector<pthread_t> tids;StartMaster(&tids);StartSlaver(&tids,5);WaitThread(tids);return 0;
}

3.生产消费模型

        生产消费模型是多线程编程中的一个经典场景,它描述了生产者线程和消费者线程之间如何共享数据缓冲区。在这个模型中,生产者线程负责创建数据并将其放入缓冲区,而消费者线程负责从缓冲区中取出数据并处理。

以下是生产消费模型的关键组成部分:

  1. 缓冲区:这是一个共享的资源,用于存储数据。它可以是一个固定大小的数组、链表或其他数据结构。

  2. 生产者线程:负责创建数据并将其放入缓冲区。生产者线程需要确保在缓冲区满时不会写入数据,以避免数据丢失。

  3. 消费者线程:负责从缓冲区中取出数据并处理。消费者线程需要确保在缓冲区空时不会读取数据,以避免空指针异常。

  4. 同步机制:为了确保生产者线程和消费者线程之间的同步,可以使用互斥锁、条件变量等同步机制。

在生产消费模型中,生产者线程和消费者线程之间通常存在以下关系:

  • 生产者线程在缓冲区未满时写入数据。
  • 消费者线程在缓冲区非空时读取数据。
  • 生产者线程和消费者线程之间通过互斥锁和条件变量进行同步。

4.阻塞队列

        阻塞队列(Blocking Queue)是一种特殊的队列,它提供了一种机制,使得生产者和消费者线程可以以不同的速度工作,而不会造成资源浪费或死锁。当队列满时,生产者线程会被阻塞,直到队列中有空闲空间;当队列空时,消费者线程会被阻塞,直到队列中有数据可消费。

以下是阻塞队列的一些关键特性:

  1. 生产者-消费者模型:阻塞队列支持生产者线程和消费者线程之间的数据传递。生产者线程将数据放入队列,而消费者线程从队列中取出数据。

  2. 线程安全:阻塞队列通常实现线程安全,可以被多个生产者和消费者线程同时访问。

  3. 阻塞机制:当队列满时,生产者线程会被阻塞,直到队列中有空闲空间;当队列空时,消费者线程会被阻塞,直到队列中有数据可消费。

  4. 无界队列和有界队列:阻塞队列可以是无界队列或有限大小的队列。无界队列没有容量限制,而有限大小的队列有最大容量限制。

#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>template <class T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _cap;}bool IsEmpty(){return _block_queue.empty();}
public:BlockQueue(int cap) : _cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_product_cond, nullptr);pthread_cond_init(&_consum_cond, nullptr);}void Enqueue(T &in) // 生产者用的接口{pthread_mutex_lock(&_mutex);if(IsFull()) //BUG??{// 生产线程去等待,是在临界区中休眠的!你现在还持有锁呢!!!// 1. pthread_cond_wait调用是: a. 让调用进程等待 b. 自动释放曾经持有的_mutex锁pthread_cond_wait(&_product_cond, &_mutex); }// 进行生产// _block_queue.push(std::move(in));std::cout << in << std::endl;_block_queue.push(in);// 通知消费者来消费pthread_cond_signal(&_consum_cond);pthread_mutex_unlock(&_mutex);}void Pop(T *out) // 消费者用的接口{pthread_mutex_lock(&_mutex);if(IsEmpty()){// 消费线程去等待,是在临界区中休眠的!你现在还持有锁呢!!!// 1. pthread_cond_wait调用是: a. 让调用进程等待 b. 自动释放曾经持有的_mutex锁pthread_cond_wait(&_consum_cond, &_mutex); }// 进行消费*out = _block_queue.front();_block_queue.pop();// 通知生产者来生产pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}private:std::queue<T> _block_queue;   // 阻塞队列int _cap;                     // 总上限pthread_mutex_t _mutex;       // 保护_block_queue的锁pthread_cond_t _product_cond; // 专门给生产者提供的条件变量pthread_cond_t _consum_cond;  // 专门给消费者提供的条件变量
};#endif

5.信号量

        在多线程编程中,信号量(Semaphore)是一种同步机制,用于控制对共享资源的访问。信号量可以用来实现互斥锁,也可以用来实现线程间的同步。

  • 信号量的值可以增加(通常使用pthread_sem_post)或减少(通常使用 pthread_sem_wait)。
  • 当信号量的值大于0时,线程可以访问共享资源;当信号量的值小于或等于0时,线程必须等待。
  • 信号量的值必须始终为非负整数。

        pthread_sem_init是 POSIX 线程(pthread)库中的一个函数,用于初始化一个信号量。信号量是一种同步机制,用于控制对共享资源的访问。它可以用来实现互斥锁,也可以用来实现线程间的同步。

int pthread_sem_init(pthread_sem_t *sem, const pthread_semattr_t *sem_attr, unsigned int value);
  • em:这是指向pthread_sem_t类型的指针,用于指定要初始化的信号量。
  • sem_attr:这是一个可选参数,指向pthread_semattr_t类型的指针,用于指定信号量的属性。如果传递NULL,则使用默认属性。
  • value:这是信号量的初始值。如果传递0,则信号量会被初始化为一个空信号量。

        pthread_sem_post是 POSIX 线程(pthread)库中的一个函数,用于增加信号量的值。

int pthread_sem_post(pthread_sem_t *sem);
  • sem:这是指向pthread_sem_t类型的指针,用于指定要增加值的信号量。

        pthread_sem_wait是 POSIX 线程(pthread)库中的一个函数,用于减少信号量的值。

int pthread_sem_wait(pthread_sem_t *sem);
  • sem:这是指向pthread_sem_t类型的指针,用于指定要减少值的信号量。

6.唤醒队列

        在多线程编程中,当一个线程因为某种原因(如等待条件变量、同步机制等)而被阻塞时,它会进入一个特定的等待队列中。这个等待队列被称为唤醒队列(Wakeup Queue),它包含了所有等待某些事件的线程。当这些事件发生时,这些线程会被唤醒,并有机会继续执行。

        唤醒队列通常与条件变量、信号量等同步机制一起使用,以确保线程在等待事件时不会竞争共享资源。当事件发生时,其他线程可以通知等待的线程,然后等待的线程会从唤醒队列中移除,并有机会继续执行。

以下是一些常见的唤醒队列实现:

  1. 条件变量唤醒队列:当条件变量被信号或信号量通知时,所有等待该条件变量的线程都会被唤醒,并重新尝试获取互斥锁。

  2. 信号量唤醒队列:当信号量的值被增加时,所有等待该信号量的线程都会被唤醒,并有机会继续执行。

  3. 等待队列:操作系统内核中通常有一个全局的等待队列,用于管理所有等待事件的线程。当事件发生时,内核会遍历这个等待队列,并唤醒所有符合条件的线程。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

[Unity Demo]从零开始制作空洞骑士Hollow Knight第七集:制作小骑士完整的冲刺Dash行为

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作一个完整的小骑士冲刺Dash行为 1.制作动画以及使用UNITY编辑器编辑2.使用代码实现完整的冲刺行为控制总结 前言 大家又好久不见&#xff08;虽然就过了…

论文速递!基于PINN的知识+数据融合方法!实现可再生能源电力系统中的TTC高效评估

本期推文将介绍一种结合知识驱动和数据驱动的混合算法在电力系统总传输能力&#xff08;TTC&#xff09;评估中的应用&#xff0c;这项研究发表于《IEEE Transactions on Power Systems》期刊&#xff0c;主要解决高比例可再生能源渗透下电力系统中的TTC快速评估问题。 荐读的论…

day21JS-npm中的部分插件使用方法

1. 静态资源目录 静态资源目录就是访问服务器的某些路劲时候&#xff0c;服务器可以吐出一个写好的指定页面。 实现思路&#xff1a; 1、先判断要找的路径是否是文件&#xff0c;如果是文件&#xff0c;就加载发给对方。 2、如果是文件夹&#xff0c;找到这个文件夹所在路径中…

linux----进程地址空间

前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、空间分布 二、栈和堆的特点 &#xff08;1&#xff09;栈堆相对而生&#xff0c;堆是向上增长的&#xff0c;栈是向下增长的。 验证&#xff1a;堆是向上增长的 这里我们看到申请的堆&#xff…

springMvc的初始配置

基础文件结构(toWeb插件) 1.导入对应依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"ht…

【软考】循环冗余校验码

目录 1. 说明2. CRC 的代码格式3. 例题3.1 例题1 1. 说明 1.循环冗余校验码(Cyclic Redundancy Check&#xff0c;CRC)广泛应用于数据通信领域和磁介质存储系统中。2.它利用生成多项式为k个数据位产生&#xff0c;r个校验位来进行编码&#xff0c;其编码长度为 kr。3.循环几余…

如何成为信息安全等级测评师?具体有哪些要求?

给大家的福利&#xff0c;点击下方蓝色字 即可免费领取↓↓↓ &#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 文章目录 前言 信息安全等级测评师 定义与作用&#xff1a;证书颁发&#xff1a;能力要求&#x…

【React】(推荐项目)一个用 React 构建的 CRUD 应用程序

推荐项目&#xff1a;CRUD 应用示例 在本篇文章中&#xff0c;我想向大家推荐一个非常实用的项目&#xff1a;CRUD 应用示例。这个项目展示了如何使用现代技术栈创建一个基础的增删改查&#xff08;CRUD&#xff09;应用&#xff0c;非常适合用于学习和实践后端开发技能。 适…

【2024华为杯数学建模研赛赛题已出(A-F题)】

华为杯2024年中国研究生数学建模竞赛A-F题已公布 A题 B题 C题 D题 E题 F题

骨传导耳机怎么选?深扒2024五款热门骨传导耳机!

耳机在我们的日常生活中渐渐变得不可或缺&#xff0c;早晨出门、通勤、工作&#xff0c;甚至睡觉时&#xff0c;它们总是陪伴在侧。尽管我们都知道长期使用耳机会对听力造成一定影响&#xff0c;但骨传导耳机的出现为我们提供了更为安全和卫生的选择。这种耳机的设计使耳朵保持…

其他比较条件

使用BETWEEN条件 可以用BETWEEN范围条件显示基于一个值范围的行。指定的范围包含一个下限和一个上限。 示例&#xff1a;查询employees表&#xff0c;薪水在3000-8000之间的雇员ID、名字与薪水。 select employee_id,last_name,salary from employees where salary between 3…

泛微E9开发 创建自定义浏览框,关联物品管理表【1】

创建自定义浏览框&#xff0c;关联物品管理表【1】 1、自定义浏览框1.1 概念1.2 前端样式 2、创建物品管理表2.1 新建建模表单操作方法2.2 物品管理表 3、创建浏览按钮 1、自定义浏览框 1.1 概念 自定义浏览框可以理解为是建模引擎中的表与表关联的一个桥梁。比如利用建模引擎…

菜鸟也能轻松上手的Java环境配置方法

初学者学习Java这么编程语言&#xff0c;第一个难题往往是Java环境的配置&#xff0c;今天与大家详细地聊一聊&#xff0c;以便大家能独立完成配置方法和过程。 首先&#xff0c;找到“JDK”&#xff0c;点击“archive”&#xff1a; 向下滑&#xff0c;在“previous java rel…

小白src挖掘 | 记某证书站的虚拟仿真实验平台

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【edusrc漏洞挖掘】 【VulnHub靶场复现】【面试分析】 &#x1f389;欢迎关注…

017_FEA_CSG_in_Matlab新的统一有限元分析工作流之2D几何

Matlab新的统一有限元分析工作流 从2023a开始&#xff0c;Matlab提供了一个统一有限元分析工作流&#xff08;UFEAW&#xff0c;unified finite element analysis workflow&#xff09;。 这个新的工作留提供一个统一的接口来求解三类问题&#xff0c;并且可以用同一套数据随…

并查集(上)

并查集简要介绍&#xff1a; 我们先讲并查集的一般使用场景&#xff0c;之后再讲并查集的具体细节以及原理。 并查集的使用一般是如下的场景&#xff1a; 一开始每个元素都拥有自己的集合&#xff0c;在自己的集合里只有这个元素自己。 f i n d ( i ) find(i) find(i)&#…

数据结构之算法复杂度

目录 前言 一、复杂度的概念 二、时间复杂度 三、大O的渐进表示法 四、空间复杂度 五、常见复杂度对比 总结 前言 本文主要讲述数据结构中的算法复杂度 一、复杂度的概念 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏…

python源代码编译exe 防止反编译的问题

1&#xff09;使用pyinstaller 打包为exe, 记住是版本是5.*&#xff0c;我用的是5.13.2 &#xff0c;不能是6.* 这是第一步。 pyinstaller -F -i d:\whs.ico packer.py -w 2&#xff09;使用pyarmor 再次加密,我使用的版本是8.3.11&#xff0c;不是7.*&#xff0c;这是第二步…

摩托车骑行行为检测系统源码分享

摩托车骑行行为检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comput…

Cursor Rules 让 Cursor AI 代码生成更智能、更高效,效率再次飞升!

最近,AI 代码生成工具越来越火,比如 Cursor AI 编辑器。很多开发者已经开始使用它来自动生成代码,以提高工作效率。不过你有没有发现,有时候 AI 自动生成的代码并不总是符合最佳实践?比如变量命名不够规范、代码风格不统一,或者生成的代码逻辑不够清晰。这些问题有时让人…