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

C++——多线程编程(从入门到放弃)

进程:运行中的程序
线程:进程中的进程
线程的最大数量取决于CPU的核心数

一、将两个函数添加到不同线程中

demo:两个函数test01()test02(),实现将用户输入的参数进行打印输出1000次
将这两个函数均放到独立的线程t1t2中,可实现独立的运作,与主函数独立执行

#include <thread>线程头文件
std::thread t1(test01, 10);创建线程t1,第一个参数为函数名,第二个参数为对应函数的参数;若函数没有参数,则不需要填入
if (t1.joinable())判断线程t1是否可以加入到主线程中,若可以加入,t1.join();则加入到主线程中
子线程加入到主线程可以保证主线程会等待子线程运行完毕之后再结束

t1.join();是将子线程t1则加入到主线程中
t1.detach();是将主线程main和子线程t1分离,确保主线程结束后,子线程仍可以再后台执行

t1.join();较为常用

#include <iostream>
#include <thread>
#include <string>void test01(int num)
{for (int i = 0; i < 1000; i++) {std::cout << "test01  " << num << " " << i << std::endl;// test01 10 0// test01 10 1// test01 10 2//...}
}void test02(std::string str) 
{for (int i = 0; i < 1000; i++){std::cout << "test02  " << str << " " << i << std::endl;// test02 hello beyond 0// test02 hello beyond 1// test02 hello beyond 2// ...}
}int main(int argc, char* argv[])
{//创建线程,把函数名和参数传递给线程std::thread t1(test01, 10);//第一个参数是函数名,第二个参数是该函数所需要的参数std::thread t2(test02, "hello beyond");//第一个参数是函数名,第二个参数是该函数所需要的参数if (t1.joinable()) //判断线程是否还在运行{//若没有将t1线程加入到主线程,则主线程结束时,t1线程也会结束,导致程序崩溃t1.join(); //等待线程结束;若不把线程t1加入到主进程中,则主进程不会等待t1进程结束之后再结束,若主进程结束了,项目结束//t1.detach(); //分离线程,主线程结束时,t1线程也会结束,不会导致程序崩溃;//join()和detach()的区别在于:join()等待线程结束,detach()分离线程,主线程结束时,t1线程不会结束,可以继续运行}if (t2.joinable()) {t2.join(); //等待线程结束;若不把线程t2加入到主进程中,则主进程不会等待t2进程结束之后再结束,若主进程结束了,项目结束}//主线程for (int i = 0; i < 1000; i++) {std::cout << "main  " << i << std::endl;}//就此开了三个线程(main()、t1、t2),三个线程并发执行return 0;
}

运行效果
在这里插入图片描述
可以看到线程t1和t2是并发执行的,执行完之后,主线程main才进行执行
这说明,在主线程main中,程序是按顺序执行的;而相互独立的线程是并行执行的

二、引用类型传入线程中

若要操作同一个变量,在函数中需要传入该变量的引用
若要加入到线程中,则需要通过std::ref(a)将变量a的引用加入到线程中,操作的都是该变量a,共享同一个变量a

#include <iostream>
#include <thread>
#include <string>void test01(int& num1) //引用传递
{for (int i = 0; i < 10; i++){num1++;}
}void test02(int num2) //值传递
{for (int i = 0; i < 10; i++){num2++;}
}int main(int argc, char* argv[])
{int num1 = 0;int num2 = 0;std::thread t1(test01, std::ref(num1));//引用传递std::thread t2(test02, num2);//值传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	std::cout << "num1 = " << num1 << std::endl;// 输出结果为10std::cout << "num2 = " << num2 << std::endl;// 输出结果为0return 0;
}

三、锁和死锁

1,现象分析:

在这里插入图片描述
俩线程都对num进行自加10000次,期间会出现t1和t2线程同时拿到num,并自加,导致结果会重复多次,出现无效的自加效果

#include <iostream>
#include <thread>
#include <string>void test01(int& num) //引用传递
{for (int i = 0; i < 100000; i++){num++;}
}void test02(int& num) //值传递
{for (int i = 0; i < 100000; i++){num++;}
}int main(int argc, char* argv[])
{int num = 0;std::thread t1(test01, std::ref(num));//引用传递std::thread t2(test02, std::ref(num));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	//按理说num应该是200000,但是由于线程并发执行,导致结果可能是100000,也可能是200000std::cout << "num = " << num << std::endl;return 0;
}

运行结果:
实际效果并不是20000
在这里插入图片描述

2,解决方法:加锁

#include <mutex>互斥锁头文件
std::mutex mtx;定义互斥锁
mtx.lock();加锁
mtx.unlock();解锁

在加锁和解锁之间的数据是不允许多个线程进行操作
当线程t1拿到num之后立马加锁,线程t2就无法获取num,直到线程t1解锁后,线程t2才可以拿到num

加锁操作保证多线程安全

#include <iostream>
#include <thread>
#include <string>
#include <mutex>std::mutex mtx;//定义互斥锁void test01(int& num) //引用传递
{for (int i = 0; i < 1000000; i++){mtx.lock();//加锁num++;mtx.unlock();//解锁}
}void test02(int& num) //值传递
{for (int i = 0; i < 1000000; i++){mtx.lock();//加锁num++;mtx.unlock();//解锁}
}int main(int argc, char* argv[])
{int num = 0;std::thread t1(test01, std::ref(num));//引用传递std::thread t2(test02, std::ref(num));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	//按理说num应该是200000,但是由于线程并发执行,导致结果可能是100000,也可能是200000std::cout << "num = " << num << std::endl;return 0;
}

运行效果:
在这里插入图片描述

3,死锁现象

在这里插入图片描述

#include <iostream>
#include <thread>
#include <string>
#include <mutex>std::mutex mtx1, mtx2;//定义互斥锁void test01(int& num1,int &num2) //引用传递
{for (int i = 0; i < 10000; i++){mtx1.lock();//加锁num1++;mtx2.lock();//加锁num2++;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}void test02(int& num1,int &num2) //值传递
{for (int i = 0; i < 10000; i++){mtx2.lock();//加锁num2++;mtx1.lock();//加锁num1++;mtx1.unlock();//解锁mtx2.unlock();//解锁}
}int main(int argc, char* argv[])
{int num1 = 0;int num2 = 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	//按理说num应该是200000,但是由于线程并发执行,导致结果可能是100000,也可能是200000std::cout << "run is over"<< std::endl;//死锁,永远不会执行std::cout << "num1=" << num1 << std::endl;std::cout << "num2=" << num2 << std::endl;return 0;
}

运行效果:
陷入死锁
在这里插入图片描述

4,死锁解决方法

方法一:要锁谁都锁谁

也就是互斥锁的先后顺序要一致,要都先锁num1就都先锁num1

#include <iostream>
#include <thread>
#include <string>
#include <mutex>std::mutex mtx1, mtx2;//定义互斥锁void test01(int& num1,int &num2) //引用传递
{for (int i = 0; i < 10000; i++){mtx1.lock();//加锁num1++;mtx2.lock();//加锁num2++;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}void test02(int& num1,int &num2) //值传递
{for (int i = 0; i < 10000; i++){mtx1.lock();//加锁num1++;mtx2.lock();//加锁num2++;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}int main(int argc, char* argv[])
{int num1 = 0;int num2 = 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	std::cout << "run is over"<< std::endl;std::cout << "num1=" << num1 << std::endl;std::cout << "num2=" << num2 << std::endl;return 0;
}

运行效果:
在这里插入图片描述

方法二:使用智能互斥锁

四、智能互斥锁

1,lock_guard

lock_guard会在其构造函数中自动加锁,在其析构函数中自动解锁
无需人为的去加锁解锁操作

std::mutex mtx1;定义互斥锁
std::lock_guard<std::mutex> lock1(mtx1);将互斥锁升级为智能互斥锁,是一个函数模板

#include <iostream>
#include <thread>
#include <string>
#include <mutex>std::mutex mtx1, mtx2;//定义互斥锁void test01(int& num1,int &num2) //引用传递
{for (int i = 0; i < 10000; i++){std::lock_guard<std::mutex> lock1(mtx1);num1++;num2++;}
}void test02(int& num1,int &num2) //值传递
{for (int i = 0; i < 10000; i++){std::lock_guard<std::mutex> lock1(mtx1);num1++;num2++;}
}int main(int argc, char* argv[])
{int num1 = 0;int num2 = 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	std::cout << "run is over"<< std::endl;std::cout << "num1=" << num1 << std::endl;std::cout << "num2=" << num2 << std::endl;return 0;
}

运行效果:
在这里插入图片描述

2,unique_lock(推荐使用)

lock_guard只支持自动加锁解锁操作
unique_lock不仅仅支持自动加锁解锁操作,还支持超时处理、延迟加锁、达到某些条件后进行加锁等操作

①将unique_lock直接替换lock_guard效果是一样的

自动加锁,自动解锁

#include <iostream>
#include <thread>
#include <string>
#include <mutex>std::mutex mtx1, mtx2;//定义互斥锁void test01(int& num1,int &num2) //引用传递
{for (int i = 0; i < 10000; i++){std::unique_lock<std::mutex> lock1(mtx1);num1++;num2++;}
}void test02(int& num1,int &num2) //值传递
{for (int i = 0; i < 10000; i++){std::unique_lock<std::mutex> lock1(mtx1);num1++;num2++;}
}int main(int argc, char* argv[])
{int num1 = 0;int num2 = 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();}	if (t2.joinable()) {t2.join();}	std::cout << "run is over"<< std::endl;std::cout << "num1=" << num1 << std::endl;std::cout << "num2=" << num2 << std::endl;return 0;
}

运行效果:
在这里插入图片描述

②手动上锁

std::unique_lock<std::mutex> lock1(mtx1,std::defer_lock);需要传入参数std::defer_lock
用户需要手动上锁,自动解锁
手动上锁是为了可以使用一些延迟上锁等互斥锁
之前的是常规互斥锁std::mutex mtx1;
例如:手动上锁的时候可以使用时间互斥锁std::timed_mutex mtx1;

std::timed_mutex mtx1;定义时间互斥锁
std::unique_lock<std::timed_mutex> lock1(mtx1, std::defer_lock);定义一个局部锁,使用defer_lock参数,表示不加锁,手动加锁,自动解锁
bool res = lock1.try_lock_for(std::chrono::milliseconds(100));尝试加锁,超时时间为100ms;如果加锁成功,则返回true,否则返回false

五、单例模式下的多线程使用

单例模式:全局只有一个实例,初始化操作只能执行一次
若在多线程中,就有可能被执行多次,此时需要使用call_once
常用的单例模式为日志类,只需要一次实例化对象

Singleton类实现了单例模式。通过std::call_once结合std::once_flag来保证Singleton类的构造函数只被调用一次,从而保证在多线程环境下只有一个Singleton实例被创建。在main函数中,创建了两个线程,这两个线程都调用Singleton::getInstance方法来获取单例实例,并调用showMessage方法。

#include <iostream>
#include <mutex>
#include <thread>class Singleton {
private:// 私有构造函数Singleton() {std::cout << "Singleton created." << std::endl;}static Singleton* instance;static std::once_flag flag;
public:// 获取单例实例的方法static Singleton* getInstance() {// 确保初始化只进行一次std::call_once(flag, []() {instance = new Singleton();});return instance;}void showMessage() {std::cout << "Singleton is working." << std::endl;}
};// 静态成员变量初始化
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;// 线程函数
void threadFunction() {Singleton* single = Singleton::getInstance();single->showMessage();
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}

六、条件变量的使用

生产者与消费者模型:
生产者(导师),消费者(导师的学生)
一个导师一般情况下会招多个学生,导师申请到国家基金或横向课题,将课题任务分成多个小块,依次分配给学生A、学生B、学生C等进行完成
导师将任务放到任务队列中,学生从任务队列中取出任务并完成,若任务队列中没有任务了,学生就可以等待一会儿(摸会儿鱼)。等导师又有新任务了,放入到任务队列中,并通知学生;学生收到通知,就不需要再等待了,开始去任务队列中取任务。
导师往任务队列中加入一个任务,因为是一个任务,所以只通知一次(notify_once),至于哪个学生去完成,导师不关心,但需要一个学生来进行完成;
导师往任务队列中加入了很多个任务,因为任务比较多,需要所有的学生进行完成(notify_all),把所有的学生都通知一遍。

极端例子,任务队列为空,导师放任务到任务队列的同时,学生来任务队列中取任务,学生发现任务队列中没任务,开始摸鱼,其实导师已经放入了一个任务

此时就需要加锁,并引入条件变量,导师往任务队列中放任务的时候加锁,放完之后发通知
学生从任务队列中取任务之前先判断任务队列中是否为空,若为空等待,直到收到导师发的通知之后再开始从任务队列中取任务

g_cv.wait(lock, bool);
参数一:互斥锁
参数二:布尔值,若为true则执行下一行,若为false则等待
一般这个bool值用lambda表达式设置

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>//引入条件变量头文件
#include <queue>//任务对列头文件std::queue<int> g_task_queue; //任务队列  导师将任务放到任务队列中,队列,先进先出,导师先放入的任务,学生先完成
std::condition_variable g_cv; //创建一个条件变量
std::mutex g_mutex; //创建一个互斥锁void Teacher()//导师(生产者),负责产生任务,并通知学生
{for (int i = 0; i < 10; i++) //导师将10个任务放入任务队列中{std::unique_lock<std::mutex> lock(g_mutex); //获取互斥锁g_task_queue.push(i); //将任务放入队列g_cv.notify_one();//通知学生,来一个学生完成这个任务//g_cv.notify_all();//通知所有学生完成这个任务std::cout << "Teacher produce task " << i << std::endl; //打印提示信息}//导师放任务到任务队列的速度别太快,睡个100msstd::this_thread::sleep_for(std::chrono::milliseconds(100)); //等待100ms,模拟学生完成任务
}void Students()//学生(消费者),负责消费任务,并等待通知
{while (true) //学生一直等待通知{std::unique_lock<std::mutex> lock(g_mutex); //获取互斥锁//等待通知,若队列为空,则阻塞等待bool is_empty = g_task_queue.empty();//判断队列是否为空//lambda表达式为ture,执行下一行;false,阻塞等待,直到条件变量调用notify_one来唤醒线程,该等待结束g_cv.wait(lock, []() {return !g_task_queue.empty(); //队列不为空,则返回true,通知学生}); //等待通知,若队列为空,则阻塞等待int task; //定义一个任务变量task = g_task_queue.front(); //取出队列头部的任务g_task_queue.pop(); //从队列中删除任务std::cout << "Student consume task " << task << std::endl; //打印提示信息}
}int main(int argc, char* argv[])
{std::thread t1(Teacher); //创建导师线程std::thread t2(Students); //创建学生线程if (t1.joinable()){t1.join(); //等待导师线程结束}if (t2.joinable()){t2.join(); //等待学生线程结束}return 0;
}

在这里插入图片描述

七、跨平台线程池

在这里插入图片描述
现准备好线程池,包括线程数组和任务队列,等待用户放任务到任务队列中,现场数组分配线程去任务队列中取任务完成

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>//引入条件变量头文件
#include <queue>//任务对列头文件
#include <vector>
#include <functional>//引入函数对象头文件class ThreadPool 
{
public:ThreadPool(int num_threads) :m_stop(false) {//往线程数组中添加num_threads个线程for (int i = 0; i < num_threads; ++i){// push_back()函数向线程数组中添加一个线程的时候会进行一个拷贝,因此这里使用emplace_back()函数,直接调用成员构造函数,避免拷贝// 比push_back()函数效率高,更加节省资源,避免了构造函数的调用m_threads.emplace_back([this](){while (true) {std::unique_lock<std::mutex> lock(m_mutex);//加锁//条件变量等待,直到有任务需要处理m_cv.wait(lock, [this]() {//如果线程池需要停止,或者任务对列为空,则返回false,通知线程等待//否则返回true,通知线程继续执行下一行代码return m_stop ||!m_tasks.empty();});//如果线程池需要停止,并且任务对列为空,则退出线程if (m_stop && m_tasks.empty()){return;}//线程池没有停止,任务对列不为空,则取出一个任务std::function<void()> task = m_tasks.front();//取出任务列表中的第一个任务m_tasks.pop();//取出任务列表中的第一个任务//取到任务之后解锁,让其他线程有机会去取任务lock.unlock();//解锁task();//执行任务}});}}~ThreadPool(){//指定锁的作用域{std::unique_lock<std::mutex> lock(m_mutex);//加锁//因为m_stop是多个线程共享的,所以在析构函数中需要加锁,确保线程安全m_stop = true;//设置线程池停止标志}m_cv.notify_all();//通知所有线程,线程池需要停止for (std::thread &t: m_threads){if (t.joinable())//如果线程还没有结束,则等待线程结束{t.join();}}}//向任务对列中添加任务template<typename F, typename... Args>//函数模板,可变参数模板void enqueue(F&& f, Args&&... args) {//&& 右值引用,可以避免拷贝,提高效率//& 左值引用,可以传引用,避免拷贝,提高效率//在函数模板里面,&& 表示万能引用(在函数模板中,&& 右值引用就是万能引用)//std::forward<F>(f)  转发函数对象,避免拷贝,提高效率std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);//使用bind函数,将任务封装成函数对象//对共享变量操作需要加锁,确保线程安全,指定锁的作用域{std::unique_lock<std::mutex> lock(m_mutex);m_tasks.emplace(std::move(task));//向任务对列中添加任务}//通知线程数组中的一个线程去取任务m_cv.notify_one();}
private:std::vector<std::thread> m_threads;//线程数组std::queue<std::function<void()>> m_tasks;//任务对列std::mutex m_mutex;//互斥锁//生产者生产任务,去通知线程数组,线程数组去指派线程去完成任务std::condition_variable m_cv;//条件变量 线程池符合生产者消费者模型,故需要条件变量;bool m_stop = false;//线程池是否停止
};int main(int argc, char* argv[])
{//10个任务(0-9),由3个线程去完成ThreadPool pool(3);//创建线程池,线程数为3//向任务对列中添加任务for (int i = 0; i < 10; ++i){pool.enqueue([i]() {std::cout << "Task " << i << " running on thread " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));//休眠1秒,模拟任务执行时间std::cout << "Task " << i << " finished on thread " << std::this_thread::get_id() << std::endl;});   }return 0;
}

运行效果:
在这里插入图片描述

八、异步并发

1,async

无需手动去创建线程,当运行到std::future<int> f1 = std::async(std::launch::async, func);时,自动会有个异步线程去调用func函数,同时开启一个线程立马执行,通过f1.get()取到函数返回的结果

#include <iostream>
#include <future>int func()
{int num = 0;for (int i = 0; i < 10000; i++){num++;}return num;
}int main(int argc, char* argv[])
{//主线程和子线程同步,只不过主线程执行完func函数后,会直接输出结果//子线程执行完func函数后,会将结果存入f中,主线程通过f.get()获取结果//子线程执行func函数std::future<int> f1 = std::async(std::launch::async, func);//执行到这行代码时,func函数已经开始执行,但是并不等待func函数的结果,而是返回一个future对象,通过这个future对象可以获取func函数的结果std::cout << "main thread result:" << func() << std::endl;//主线程执行func函数,并打印结果//以上两者是同时执行的,主线程和子线程同步std::cout<< "async result:" << f1.get() << std::endl;//会将return的结果存放到f1中,通过f1.get()获取结果return 0;
}

运行效果:
在这里插入图片描述

2,future

async会立马自动调用线程执行,而future需要手动创建线程执行

std::packaged_task<int()> task(func);创建packaged_task对象task,包装func函数
std::future<int> f = task.get_future();创建future对象f,用于获取func函数的结果
std::thread t(std::move(task));将task的控制权移交给t线程,t线程负责执行task函数,并将结果存入f中

#include <iostream>
#include <future>int func()
{int num = 0;for (int i = 0; i < 10000; i++){num++;}return num;
}int main(int argc, char* argv[])
{std::packaged_task<int()> task(func);//创建task对象,包装func函数std::future<int> f = task.get_future();//创建future对象f,用于获取func函数的结果std::thread t(std::move(task));//将task的控制权移交给t线程,t线程负责执行task函数,并将结果存入f中;子线程t执行func函数,与主线程同步std::cout << "main thread result:" << func() << std::endl;//主线程执行func函数,并打印结果if (t.joinable()) {t.join();//等待子线程t线程结束}std::cout << "packaged_task result:" << f.get() << std::endl;//获取f中存放的func函数的结果return 0;
}

运行效果:
在这里插入图片描述

3,promise

场景:在主线程中获取子线程中通过promise所设置的值

#include <iostream>
#include <future>void func(std::promise<int>& prom)
{prom.set_value(42);//设置promise的值
}int main(int argc, char* argv[])
{std::promise<int> prom;//声明一个promise对象std::future<int> fut = prom.get_future();//promise对象会返回一个future对象,用于获取promise的值std::thread t(func, std::ref(prom));启动一个线程,并传入promise对象作为参数if (t.joinable()) {t.join();}std::cout << fut.get() << std::endl;//获取得到promise所设置的值return 0;
}

运行效果:
在这里插入图片描述

九、原子操作

atomic原子操作是一个模板类,用于在多线程中访问和修改共享变量,避免多线程中所出现的数据竞争现象的发生
原子操作所处理的都是同一类问题
把变量设置为atomc类型,该变量自身自带线程安全操作,会自动加锁和解锁,且要比手动加锁解锁的速度更快

#include <iostream>
#include <thread>
#include <atomic>std::atomic<int> g_num_atomic = 0;//原子变量
int g_num = 0;//非原子变量void func()
{for (int i = 0; i < 1000000; ++i) {g_num_atomic++;g_num++;}
}int main(int argc, char* argv[])
{std::thread t1(func);std::thread t2(func);if (t1.joinable()) t1.join();if (t2.joinable()) t2.join();std::cout << "g_num = " << g_num << std::endl;//输出非原子变量的值,因为g_num是非原子变量,所以可能出现数据竞争,导致结果不准确std::cout << "g_num_atomic = " << g_num_atomic << std::endl;//输出原子变量的值return 0;
}

运行效果:
全局变量g_num是一个int,由于是多线程操作,故出现了数据竞争问题,导致数据不对
全局变量g_num_atomic是一个int型的原子变量,该变量会自动加锁和解锁,不会发生数据竞争问题
在这里插入图片描述

学习笔记参考来源:陈子青——C++11 多线程编程-小白零基础到手撕线程池
若有侵权联系立删

相关文章:

C++——多线程编程(从入门到放弃)

进程&#xff1a;运行中的程序 线程&#xff1a;进程中的进程 线程的最大数量取决于CPU的核心数 一、将两个函数添加到不同线程中 demo&#xff1a;两个函数test01()和test02()&#xff0c;实现将用户输入的参数进行打印输出1000次 将这两个函数均放到独立的线程t1和t2中&…...

江协科技STM32学习- P14 示例程序(定时器定时中断和定时器外部时钟)

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…...

2024年CSP-J认证 CCF信息学奥赛C++ 中小学初级组 第一轮真题-阅读程序题解析

2024 CCF认证第一轮&#xff08;CSP-J&#xff09;真题 二、阅读程序题 (程序输入不超过数组或字符串定义的范围&#xff0c;判断题正确填√错误填X;除特殊说明外&#xff0c;判断题 1.5分&#xff0c;选择题3分&#xff0c;共计40 分) 第一题 01 #include <iostream>…...

Hive ROW_NUMBER() 简介

在 Apache Hive 中&#xff0c;ROW_NUMBER() 是一个窗口函数&#xff0c;常用于为查询结果中的每一行生成唯一的行号。它在 SQL 查询结果集中按照指定的排序规则对每一行进行编号。ROW_NUMBER() 的实现依赖于 Hive 的分布式执行框架和排序机制。 为了理解 ROW_NUMBER() 的底层实…...

java是干什么的

Java 是一种广泛使用的编程语言&#xff0c;主要用于以下几个方面&#xff1a; Web 开发&#xff1a;Java 可以用于创建动态网页和 Web 应用程序&#xff0c;常见的框架有 Spring 和 JavaServer Faces&#xff08;JSF&#xff09;。 企业级应用&#xff1a;Java 被广泛应用于…...

AI与量化投资人才培养计划-连接职场 助力走在金融行业前沿

AI与量化投资人才培养计划-连接职场 助力走在金融行业前沿 人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;量化投资已逐渐成为金融行业的新趋势&#xff0c;对专业人才的需求日益迫切。本文将深入探讨一项针对AI与量化投资的人才培养计划&#xff0c;旨在为金融专业…...

《CUDA编程》2.CUDA中的线程组织

0 来自GPU的hello world 在visua studio 中新建一个CUDA runtime项目&#xff0c;然后把kernel.cu中的代码删掉&#xff0c;输入以下代码 #include"cuda_runtime.h" #include"device_launch_parameters.h"#include<stdio.h>__global__ void hello_…...

学习篇 | Dockerized GitLab 安装使用(简单实操版)

1. 详细步骤 1.1 安装启动 postgresql 服务 docker pull sameersbn/postgresql:14-20230628docker run --name gitlab-postgresql -d \--env DB_NAMEgitlabhq_production \--env DB_USERgitlab --env DB_PASSpassword \--env DB_EXTENSIONpg_trgm,btree_gist \--volume /srv/…...

Linux服务器磁盘扩容

文章目录 扩容挂载 扩容 [rootserver8 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sr0 11:0 1 1024M 0 rom vda 252:0 0 1T 0 disk ├─vda1 252:1 0 1G 0 par…...

Redis的一些数据类型(一)

&#xff08;一&#xff09;数据类型 我们说redis是key value键值对的方式存储数据&#xff0c;key是字符串&#xff0c;而value是一些数据结构,那今天就来说一下value存储的数据。 我们数据结构包含&#xff0c;String&#xff0c;hash&#xff0c;list&#xff0c;set和zest但…...

论文复现:考虑电网交互的风电、光伏与电池互补调度运行(MATLAB-Yalmip-Cplex全代码)

论文复现:考虑电网交互的风电、光伏与电池储能互补调度运行(MATLAB-Yalmip-Cplex全代码) 针对风电、光伏与电化学储能电站互补运行的问题,已有大量通过启发式算法寻优的案例,但工程上更注重实用性和普适性。Yalmip工具箱则是一种基于MATLAB平台的优化软件工具箱,被广泛应用…...

HTTP 协议介绍

基本介绍&#xff1a; HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP 是一种应用层协议&#xff0c;是基…...

解决windows上VMware的ubuntu虚拟机不能拷贝和共享

困扰多时的VMware虚拟机不能复制拷贝和不能看到共享文件夹的问题&#xff0c;终于解决了~ 首先确定你已经开启了复制拷贝和共享文件夹&#xff0c;并且发现不好用。。。 按照下面方式解决这个问题。 1&#xff0c;删除当前的vmware tools。 sudo apt-get remove --purge ope…...

Python+rust会是一个强大的组合吗?

今天想和大家讨论一个在技术圈子里越来越火的话题——Python和Rust的组合。 不少程序员都开始探索这两个语言的结合&#xff0c;希望能借助Python的简洁和Rust的高性能&#xff0c;来打造出既易用又强大的软件。 那么&#xff0c;这对CP&#xff08;编程组合&#xff09;真的…...

引用和指针的区别

引用&#xff08;reference&#xff09;和指针&#xff08;pointer&#xff09;都是 C 中用来间接访问内存中对象的机制&#xff0c;但它们有一些重要的区别。以下是它们在语法、用法和特性上的详细区别。 下面从7个方面来详细说明引用和指针的区别 1. 定义与语法区别 引用&…...

内容生态短缺,Rokid AR眼镜面临市场淘汰赛

AR是未来&#xff0c;但在技术路径难突破、生态系统难建设&#xff0c;且巨头纷纷下场的背景下&#xff0c;Rokid能坚持到黎明吗&#xff1f; 转载&#xff1a;科技新知 原创 作者丨王思原 编辑丨蕨影 苹果Vision Pro的成功量产和发售&#xff0c;以及热门游戏《黑神话》等在A…...

【论文阅读】StoryMaker | 更全面的人物一致性开源工作

文章目录 1 Motivation2 背景 相关工作 Related work3 Method 方法4 效果 1 Motivation 背景是 Tuning-free personalized image generation methods无微调的个性化图像生成方式在维持脸部一致性上取得了显著性的成功。这里我不是很了解 然而&#xff0c;在多个场景中缺乏整…...

读构建可扩展分布式系统:方法与实践14流处理系统

1. 流处理系统 1.1. 时间就是金钱 1.1.1. 从数据中提取有价值的知识和获得洞见的速度越快&#xff0c;就能越快地响应系统所观察的世界的变化 1.1.2. 信用卡欺诈检测 1.1.3. 网络安全中异常网络流量的捕获 1.1.4. 在支持GPS的驾驶应用程序中进行的实时路线规划 1.1.5. 社交…...

C++第2课——取余运算符的应用、浮点型和字符型(含视频讲解)

文章目录 1、课程笔记2、课程视频 1、课程笔记 /* #include<iostream> using namespace std; int main(){//cout<<"hello,world!";//运算符的优先级 () * / % -// 3/2 1...1 3%21 5%32 3%53 -3%2-1 3%-21//cout<<6/4%2;//int 向下取整6…...

SQL常用技巧总结

查询优化基本准则 1、ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名&#xff0c;因此 FROM 子句中写在最后的表(基础表 driving table)将被最先处理。 在FROM 子句中包含多个表的情况下&#xff0c;你必须选择记录条数最少的表作为基础表。 例如&#xff1a; 表 T…...

AJAX(简介以及一些用法)

AJAX 1. 简介 什么是 Ajax Ajax 的全称是 Asynchronous JavaScript And XML &#xff08;异步 JavaScript 和 XML &#xff09;我们可以理解为&#xff1a;在网页中 利用 XMLHttpRequest 对象和服务器进行数据交互的方式就是 Ajax &#xff0c;它可以帮助我们轻松实现网页…...

美畅物联丨GB/T 28181系列之TCP/UDP被动模式和TCP主动模式

GB/T 28181《安全防范视频监控联网系统信息传输、交换、控制技术要求》作为我国安防领域的重要标准&#xff0c;为视频监控系统的建设提供了全面的技术指导和规范。该标准详细规定了视频监控系统的信息传输、交换和控制技术要求&#xff0c;在视频流传输方面&#xff0c;GB/T 2…...

机器学习之实战篇——图像压缩(K-means聚类算法)

机器学习之实战篇——图像压缩(K-means聚类算法&#xff09; 0. 文章传送1.实验任务2.实验思想3.实验过程 0. 文章传送 机器学习之监督学习&#xff08;一&#xff09;线性回归、多项式回归、算法优化[巨详细笔记] 机器学习之监督学习&#xff08;二&#xff09;二元逻辑回归 …...

轴承介绍以及使用

轴承&#xff08;Bearing&#xff09;是在机械传动过程中起固定、旋转和减小载荷摩擦系数的部件。也可以说&#xff0c;当其它机件在轴上彼此产生相对运动时&#xff0c;用来降低运动力传递过程中的摩擦系数和保持转轴中心位置固定的机件。 轴承是当代机械设备中一种举足轻重的…...

【JAVA】算法笔记

一、ArrayList ArrayList类是一个可以动态变化的数组&#xff0c;与普通数组的区别就是它没有固定的长度。 ArrayList<String> arrList new ArrayList<String>(); arrList.add("吐泡泡"); System.out.println(arrList.get(0)); arrList.set(0,"J…...

Gnu Radio抓取WiFi信号,流程图中模块功能

模块流程如图所示&#xff1a; GNURadio中抓取WiFi信号的流程图中各个模块的功能&#xff1a; UHD: USRP Source&#xff1a; 使用此模块配置USRP硬件进行信号采集。设置频率、增益、采样率等参数。Complex to Mag^2&#xff1a; 将复数IQ数据转换为幅度的平方。Delay&#xf…...

GO语言中make与new的区别

区别 1 make不仅分配内存&#xff0c;还会初始化。 new只会分配零值填充的值2make只适用slice,map,channel的数据&#xff0c;new 没有限制3make返回原始类型(T),new返回类型的指针(*T) 源码中定义的区别 func make(t Type,size …IntegerType) Type func new(Type) *Type f…...

安全运维类面试题

1、你熟悉哪些品牌的安全设备 答&#xff1a;天融信的ngfw防火墙&#xff0c;老牌防火墙厂商&#xff0c;功能比较齐全&#xff0c;像流量检测&#xff0c;web应用防护和僵木蠕等模块都有&#xff0c;界面是红白配色&#xff0c;设计稍微有点老 2、IPS用的是哪个牌子的 答&…...

STM32外设之LTDC/DMA2D—液晶显示(野火)

文章目录 显示屏有几种?基本参数控制?显存 LTDC 液晶控制器LTDC 结构框图LTDC 初始化结构体 LTDC_InitTypeDefLTDC 层级初始化结构体 DMA2D 图形加速器DMA2D 初始化结构体 要了解什么 屏幕是什么&#xff0c;有几种屏&#xff0c;有什么组成。 怎么控制&#xff0c;不同屏幕控…...

调试vue build之后的js文件

调试 dist 目录下的 JavaScript 文件可以按照以下步骤进行&#xff1a; 1. 确保 Source Maps 正常生成 确认你的构建配置中已启用 Source Maps&#xff0c;确保 .map 文件与构建后的 .js 文件位于同一目录。 2. 启动一个本地服务器 使用本地服务器来服务 dist 目录&#xf…...