C++ STL 多线程库用法介绍

目录

一:Atomic:

二:Thread

 1. 创建线程 

2. 小心移动(std::move)线程 

 3. 如何创建带参数的线程

4. 线程参数是引用类型时,要小心谨慎。

 5. 获取线程ID

6. jthread

7. 如何在线程中使用中断 stop_token

三:如何解决数据竞争

1.有问题的代码 

2.使用互斥 

3.预防死锁

4. 自动释放锁

5. 延迟锁

6. 共享锁

7. 线程安全的初始化

四:线程局部存储

五:线程通信

1.条件变量

2. 防止虚假唤醒

3. 防止唤醒丢失

4.信号量

5. std::latch

六:任务

 1. std::promise, std::future

2. 用std::promise, std::future进行线程同步

3. std::async

4. std::package_task


一:Atomic:

#include <atomic>
#include <thread>
#include <iostream>

using namespace std;

std::atomic_int x, y;
int r1, r2;
void writeX() {
	x.store(1);
	r1 = y.load();
}
void writeY() {
	y.store(1);
	r2 = x.load();
} 

int main() {
	for (int i = 0; i < 100; i++)
	{
		x = 0;
		y = 0;
		std::thread a(writeX);
		std::thread b(writeY);
		a.join();
		b.join();
		std::cout << r1 << r2 << std::endl;
	}
	return 0;
}
//可能的输出有三种情况:01, 10, 11
//01:先执行线程a, 再执行线程b
//10:先执行线程b,再执行线程a
//11:执行线程a一半后调度到线程b,然后再回来  

二:Thread

 1. 创建线程 
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;

void helloFunction() {
	cout << "function" << endl;
}


class HelloFunctionObject {
public:
	void operator()() const {
		cout << "function object" << endl;
	}
};


int main()
{
	thread t1(helloFunction); // function
	HelloFunctionObject helloFunctionObject;
	thread t2(helloFunctionObject); // function object
	thread t3([] { cout << "lambda function" << std::endl; }); // lambda function

	t1.join(); //需要用join,否则可能会出现主线程退出时,t1线程还没有执行完的情况,引起异常
	t2.join();
	t3.join();
	return 0;
}
2. 小心移动(std::move)线程 
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;

int main()
{
	std::thread t([] { cout << "lambda function"; });
	std::thread t2;
	t2 = std::move(t);
	std::thread t3([] { cout << "lambda function"; });
	/*此处代码有问题,当t2 已经获得线程t后,它已经是callable和joinable,再赋值t3会terminate*/ 
	t2 = std::move(t3);  std::terminate
}
 3. 如何创建带参数的线程
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;

//如何在线程中传递参数
void printStringCopy(string s) { cout << s; }
void printStringRef(const string& s) { cout << s; }

int main()
{
	string s{ "C++" };
	thread tPerCopy([=] { cout << s; }); // C++
	thread tPerCopy2(printStringCopy, s); // C++
	tPerCopy.join();
	tPerCopy2.join();
	thread tPerReference([&] { cout << s; }); // C++
	thread tPerReference2(printStringRef, s); // C++
	tPerReference.join();
	tPerReference2.join(); 
}
4. 线程参数是引用类型时,要小心谨慎。
#include <iostream>

using namespace std;

using std::this_thread::sleep_for;
using std::this_thread::get_id;

struct Sleeper {
	Sleeper(int& i_) :i{ i_ } {};
	void operator() (int k) {
		for (unsigned int j = 0; j <= 5; ++j) {
			sleep_for(std::chrono::milliseconds(100));
			i += k;
		}
		std::cout << get_id(); // undefined behaviour
	}
private:
	int& i;
};


int main()
{

	int valSleeper = 1000;
	//valSleeper 作为引用类型传给线程,如果主线程先退出,t线程使用valSleeper会产生未定义行为, 并且主线程和t线程共享varSleeper,产生数据竞争,
	std::thread t(Sleeper(valSleeper), 5); 
	t.detach();
	std::cout << valSleeper; // undefined behaviour
}
 5. 获取线程ID
using namespace std;
using std::this_thread::get_id;

int main()
{
	std::cout << std::thread::hardware_concurrency() << std::endl; // 4
	std::thread t1([] { std::cout << get_id() << std::endl; }); // 139783038650112
	std::thread t2([] { std::cout << get_id() << std::endl; }); // 139783030257408
	std::cout << t1.get_id() << std::endl; // 139783038650112
	std::cout << t2.get_id() << std::endl; // 139783030257408

	t1.swap(t2);
	std::cout << t1.get_id() << std::endl; // 139783030257408
	std::cout << t2.get_id() << std::endl; // 139783038650112
	std::cout << get_id() << std::endl; // 140159896602432
    
	t1.join();
	t2.join();
}
6. jthread
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;
using std::this_thread::get_id;

//jthread 自动join()的线程
int main()
{
	std::jthread thr{ [] { std::cout << "std::jthread" << "\n"; } }; // std::jthread
	std::cout << "thr.joinable(): " << thr.joinable() << "\n"; // thr.joinable(): true
}
7. 如何在线程中使用中断 stop_token
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;
using std::this_thread::get_id;
using namespace::std::literals;//字面量,比如0.2s, C++20能识别这种写法

std::jthread nonInterruptable([] { // (1)  创建非中断线程
	int counter{ 0 };
while (counter < 10) {
	std::this_thread::sleep_for(0.2s);
	std::cerr << "nonInterruptable: " << counter << std::endl;
	++counter;
}
	});
std::jthread interruptable([](std::stop_token stoken) { // (2) 创建可中断线程
	int counter{ 0 };
while (counter < 10) {
	std::this_thread::sleep_for(0.2s);
	if (stoken.stop_requested()) return; // (3) 检查线程是否被中断
	std::cerr << "interruptable: " << counter << std::endl;
	++counter;
}
	});

int main()
{
	std::this_thread::sleep_for(1s);
	std::cerr << "Main thread interrupts both jthreads" << std::endl;
	nonInterruptable.request_stop(); // (4)//请求中断,非中断线程不理会
	interruptable.request_stop();//请求中断,中断线程会响应
}

三:如何解决数据竞争

1.有问题的代码 
#include <atomic>
#include <thread>
#include <iostream>

using namespace std;

struct Worker {
	Worker(string n) :name(n) {};
	void operator() () {
		for (int i = 1; i <= 3; ++i) {
			this_thread::sleep_for(chrono::milliseconds(200));
            //流本身是线程安全的,但是cout是共享变量,它会独占流,多个线程访问cout时会引起数据竞争 
			cout << name << ": " << "Work " << i << endl;
		}
	}
private:
	string name;
};


int main()
{
	thread herb = thread(Worker("Herb"));
	thread andrei = thread(Worker(" Andrei"));
	thread scott = thread(Worker(" Scott"));
	thread bjarne = thread(Worker(" Bjarne"));

	herb.join();
	andrei.join();
	scott.join();
	bjarne.join();

}
2.使用互斥 
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>

using namespace std;

std::mutex mutexCout;

struct Worker {
	Worker(string n) :name(n) {};
	void operator() () {
		for (int i = 1; i <= 3; ++i) {
			this_thread::sleep_for(chrono::milliseconds(200));
			mutexCout.lock();
			cout << name << ": " << "Work " << i << endl;
			mutexCout.unlock();
		}
	}
private:
	string name;
};

int main()
{
	thread herb = thread(Worker("Herb"));
	thread andrei = thread(Worker("Andrei"));
	thread scott = thread(Worker("Scott"));
	thread bjarne = thread(Worker("Bjarne"));

	herb.join();
	andrei.join();
	scott.join();
	bjarne.join();

}
3.预防死锁
m.lock();
sharedVar= getVar(); //如果此处抛出异常,会导致m.unlock未调用,锁不能被释放,其他线程无法得到锁,进而可能产生死锁
m.unlock()
#include <iostream>
#include <mutex>

using namespace std;

struct CriticalData {
	std::mutex mut;
};
void deadLock(CriticalData& a, CriticalData& b) {
	a.mut.lock();
	std::cout << "get the first mutex\n";
	std::this_thread::sleep_for(std::chrono::milliseconds(1));
	b.mut.lock();
	std::cout << "get the second mutex\n";
	a.mut.unlock(), b.mut.unlock();
}

int main()
{
	CriticalData c1;
	CriticalData c2;
	//t1, t2在拿到锁后都在等对方释放锁
	std::thread t1([&] { deadLock(c1, c2); });
	std::thread t2([&] { deadLock(c2, c1); });
	t1.join();
	t2.join();
}
4. 自动释放锁
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>

using namespace std;

std::mutex mutexCout;
struct Worker {
	Worker(std::string n) :name(n) {};
	void operator() () {
		for (int i = 1; i <= 3; ++i) {
			std::this_thread::sleep_for(std::chrono::milliseconds(200));
			std::lock_guard<std::mutex> myLock(mutexCout);//自动释放锁
			std::cout << name << ": " << "Work " << i << std::endl;
		}
	}
private:
	std::string name;
};

int main()
{
	thread herb = thread(Worker("Herb"));
	thread andrei = thread(Worker("Andrei"));
	thread scott = thread(Worker("Scott"));
	thread bjarne = thread(Worker("Bjarne"));

	herb.join();
	andrei.join();
	scott.join();
	bjarne.join();
}
5. 延迟锁
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>

using namespace std;

using namespace std;
struct CriticalData {
	mutex mut;
};
void deadLockResolved(CriticalData& a, CriticalData& b) {
	unique_lock<mutex>guard1(a.mut, defer_lock);
	cout << this_thread::get_id() << ": get the first lock" << endl;
	this_thread::sleep_for(chrono::milliseconds(1));
	unique_lock<mutex>guard2(b.mut, defer_lock);
	cout << this_thread::get_id() << ": get the second lock" << endl;
	cout << this_thread::get_id() << ": atomic locking" << endl;
	lock(guard1, guard2);
}

int main()
{
	CriticalData c1;
	CriticalData c2;
	thread t1([&] { deadLockResolved(c1, c2); });
	thread t2([&] { deadLockResolved(c2, c1); });

	t1.join();
	t2.join();
}
6. 共享锁
#include <mutex>
...
std::shared_timed_mutex sharedMutex;
std::unique_lock<std::shared_timed_mutex> writerLock(sharedMutex);
std::shared_lock<std::shared_time_mutex> readerLock(sharedMutex);
std::shared_lock<std::shared_time_mutex> readerLock2(sharedMutex);
7. 线程安全的初始化
//常量表达式是线程安全的
struct MyDouble{
constexpr MyDouble(double v):val(v){};
constexpr double getValue(){ return val; }
private:
double val
};
constexpr MyDouble myDouble(10.5);
std::cout << myDouble.getValue(); // 10.5
//块内静态变量
void blockScope(){
static int MySharedDataInt= 2011;
}
//once_flag, call_once 
#include <mutex>
...
using namespace std;
once_flag onceFlag;
void do_once(){
call_once(onceFlag, []{ cout << "Only once." << endl; });
}
thread t1(do_once);
thread t2(do_once);

四:线程局部存储


std::mutex coutMutex;
thread_local std::string s("hello from ");
void addThreadLocal(std::string const& s2){
s+= s2;
std::lock_guard<std::mutex> guard(coutMutex);
std::cout << s << std::endl;
std::cout << "&s: " << &s << std::endl;
std::cout << std::endl;
}
std::thread t1(addThreadLocal, "t1");
std::thread t2(addThreadLocal, "t2");
std::thread t3(addThreadLocal, "t3");
std::thread t4(addThreadLocal, "t4");

五:线程通信

1.条件变量
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>

using namespace std;

std::mutex mutex_;
std::condition_variable condVar;
bool dataReady = false;
void doTheWork() {
	std::cout << "Processing shared data." << std::endl;
}
void waitingForWork() {
	std::cout << "Worker: Waiting for work." << std::endl;
	std::unique_lock<std::mutex> lck(mutex_);
	condVar.wait(lck, [] { return dataReady; });
	doTheWork();
	std::cout << "Work done." << std::endl;
}
void setDataReady() {
	std::lock_guard<std::mutex> lck(mutex_);
	dataReady = true;
	std::cout << "Sender: Data is ready." << std::endl;
	condVar.notify_one();
}

int main()
{
	std::thread t1(waitingForWork);
	std::thread t2(setDataReady);
	t1.join();
	t2.join();
}
2. 防止虚假唤醒
//为了防止虚假唤醒,在唤醒前应进行条件检查,且发送方应将条件置为true。
//dataReady = true; //发送方设置条件满足
//[] { return dataReady; } //接收方进行条件检查
3. 防止唤醒丢失
//如果发送方在接收方等待之前,就发送了唤醒,可能会导致唤醒丢失,因此要做两件事:
//1: 要先等待,后发送唤醒
//2: 在接收方的等待函数中要检查是否满足条件 [] { return dataReady; };
4.信号量
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <semaphore>
#include <vector>

using namespace std;

std::vector<int> myVec;

std::counting_semaphore<1> prepareSignal(0); // (1)
void prepareWork() {
	myVec.insert(myVec.end(), { 0, 1, 0, 3 });
	std::cout << "Sender: Data prepared." << '\n';
	prepareSignal.release(); // (2)
}

void completeWork() {
	std::cout << "Waiter: Waiting for data." << '\n';
	prepareSignal.acquire(); // (3)
	myVec[2] = 2;
	std::cout << "Waiter: Complete the work." << '\n';
	for (auto i : myVec) std::cout << i << " ";
	std::cout << '\n';
}


int main()
{
	std::thread t1(prepareWork);
	std::thread t2(completeWork);
	t1.join();
	t2.join();
}
5. std::latch
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <semaphore>
#include <vector>
#include <latch>

using namespace std;

std::mutex coutMutex;

std::latch workDone(2);
std::latch goHome(1); // (5)
void synchronizedOut(const std::string s) {
	std::lock_guard<std::mutex> lo(coutMutex);
	std::cout << s;
}


class Worker {
public:
	Worker(std::string n) : name(n) { };
	void operator() () {
		// notify the boss when work is done
		synchronizedOut(name + ": " + "Work done!\n");
		workDone.count_down(); // (3) 完成工作
		// waiting before going home
		goHome.wait();//等待老板发命令让他们回家
		synchronizedOut(name + ": " + "Good bye!\n");
	}
private:
	std::string name;
};



int main()
{
	std::cout << "BOSS: START WORKING! " << '\n';
	Worker herb(" Herb"); // (1) 工人1
	std::thread herbWork(herb); //工人1必须完成自己的工作
	Worker scott(" Scott"); // (2) 工人2
	std::thread scottWork(scott);//工人2必须完成自己的工作
	workDone.wait(); // (4) 完成工作后等待
	std::cout << '\n';
	goHome.count_down();//老板发命令回家
	std::cout << "BOSS: GO HOME!" << '\n';
	herbWork.join();
	scottWork.join();
}

6. std::barrier

#include <barrier>
#include <iostream>
#include <string>
#include <syncstream>
#include <thread>
#include <vector>

int main()
{
    const auto workers = { "Anil", "Busara", "Carl" };

    auto on_completion = []() noexcept
    {
        // locking not needed here
        static auto phase =
            "... done\n"
            "Cleaning up...\n";
        std::cout << phase;
        phase = "... done\n";
    };

    std::barrier sync_point(std::ssize(workers), on_completion);

    auto work = [&](std::string name)
    {
        std::string product = "  " + name + " worked\n";
        std::osyncstream(std::cout) << product;  // ok, op<< call is atomic
        sync_point.arrive_and_wait();

        product = "  " + name + " cleaned\n";
        std::osyncstream(std::cout) << product;
        sync_point.arrive_and_wait();
    };

    std::cout << "Starting...\n";
    std::vector<std::jthread> threads;
    threads.reserve(std::size(workers));
    for (auto const& worker : workers)
        threads.emplace_back(work, worker);
}

六:任务

 1. std::promise, std::future
#include <future>
#include <iostream>

void product(std::promise<int>&& intPromise, int a, int b) {
	intPromise.set_value(a * b);
}
int main()
{
	int a = 20;
	int b = 10;
	std::promise<int> prodPromise;
	std::future<int> prodResult = prodPromise.get_future();
	std::jthread prodThread(product, std::move(prodPromise), a, b);
	std::cout << "20*10= " << prodResult.get(); // 20*10= 200
}
2. 用std::promise, std::future进行线程同步
#include <future>
#include <iostream>

void doTheWork() {
	std::cout << "Processing shared data." << std::endl;
}
void waitingForWork(std::future<void>&& fut) {
	std::cout << "Worker: Waiting for work." <<
		std::endl;
	fut.wait();
	doTheWork();
	std::cout << "Work done." << std::endl;
}
void setDataReady(std::promise<void>&& prom) {
	std::cout << "Sender: Data is ready." <<
		std::endl;
	prom.set_value();
}

int main()
{
	std::promise<void> sendReady;
	auto fut = sendReady.get_future();
	std::jthread t1(waitingForWork, std::move(fut));
	std::jthread t2(setDataReady, std::move(sendReady));

}
3. std::async
#include <future>
#include <iostream>

using std::chrono::duration;
using std::chrono::system_clock;
using std::launch;

int main()
{
	auto begin = system_clock::now();
	auto asyncLazy = std::async(launch::deferred, [] { return system_clock::now(); });
	auto asyncEager = std::async(launch::async, [] { return system_clock::now(); });


	std::this_thread::sleep_for(std::chrono::seconds(1));
	auto lazyStart = asyncLazy.get() - begin;
	auto eagerStart = asyncEager.get() - begin;
	auto lazyDuration = duration<double>(lazyStart).count();
	auto eagerDuration = duration<double>(eagerStart).count();
	std::cout << lazyDuration << " sec"; // 1.00018 sec.
	std::cout << eagerDuration << " sec"; // 0.00015489 sec.
}
#include <future>
#include <iostream>
#include <thread>

using std::chrono::duration;
using std::chrono::system_clock;
using std::launch;

int main()
{
	int res;
	std::thread t([&] { res = 2000 + 11; });
	t.join();
	std::cout << res << std::endl; // 2011


	auto fut = std::async([] { return 2000 + 11; });//异步调用
	std::cout << fut.get() << std::endl; // 2011
}
4. std::package_task
#include <future>
#include <iostream>
#include <queue>
#include <thread>

using namespace std;
using std::chrono::duration;
using std::chrono::system_clock;
using std::launch;

struct SumUp {
	int operator()(int beg, int end) {
		for (int i = beg; i < end; ++i) sum += i;
		return sum;
	}
private:
	int beg;
	int end;
	int sum{ 0 };
};


int main()
{
	SumUp sumUp1, sumUp2;
	packaged_task<int(int, int)> sumTask1(sumUp1);//任务1
	packaged_task<int(int, int)> sumTask2(sumUp2);//任务2
	future<int> sum1 = sumTask1.get_future(); //任务1的结果
	future<int> sum2 = sumTask2.get_future(); //任务2的结果
	deque< packaged_task<int(int, int)>> allTasks; //存储所有的任务
	allTasks.push_back(move(sumTask1));//将任务1加入队列
	allTasks.push_back(move(sumTask2));//将任务2加入队列
	int begin{ 1 };
	int increment{ 5000 };
	int end = begin + increment;
	while (not allTasks.empty()) {
		packaged_task<int(int, int)> myTask = move(allTasks.front());//取出1个任务
		allTasks.pop_front();
		thread sumThread(move(myTask), begin, end);//执行这个任务
		begin = end;
		end += increment;
		sumThread.detach();
	}
	auto sum = sum1.get() + sum2.get();//查询任务的结果
	cout << sum;

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大前端热点技术

前言 2018年后&#xff0c;整个大前端发展趋于稳定&#xff0c;各大主流框架的特性变少&#xff0c;各种新轮子也在逐渐变少&#xff0c;但在多端融合、上下游提效以及一些细分领域&#xff0c;还是有很多值得期待的。 本文将基于过去一年大前端方向在Web、Node、多端、IoT、…

vue学习笔记之组件传值

说起组件传值&#xff0c;首先要介绍再vue中什么是组件。 组件&#xff08;Component&#xff09;&#xff0c;是vue中很强大的一个功能&#xff0c;可以将一些可重用的代码进行重用。所有的vue组件同时也是vue实例&#xff0c;可以接受使用相同的选项对象和提供相同的生命周期…

[Unity入门01] Unity基本操作

参考的傅老师的教程学了一下Unity的基础操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移动&#xff1a;鼠标中键旋转&#xff1a;鼠标右键放大&#xff1a;鼠标滚轮飞行模式&#xff1a;右键WASDQEFocus模式&…

【机器学习】属性降维:揭示数据的简化之美

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 属性降维&#xff1a;揭示数据的简化之美引言什么是属性降维&#xff1f;为何降…

linux学习week2+3

linux学习 九.linux磁盘分区、挂载 3.磁盘情况查询 命令&#xff1a;df -h 注意&#xff1a;使用率到80%以上就要清理了 查询指定目录的磁盘占用情况&#xff1a;du -d 目录 其它参数&#xff1a; -s&#xff1a;指定目录占用大小汇总 -h&#xff1a;带计量单位 -a&#xff…

lora/lycoris

Stable Diffusion 训练指南 (LyCORIS) | Coding HuskyStable Diffusion 文字生成图片的教程已经很多了。这篇文章是讲解如何用 Kohya Trainer 在 Google Colab 上训练一个 LyCORIS 模型。在读之前希望你已经至少玩过 Stable Diffusion。https://ericfu.me/stable-diffusion-fin…

QFileSystemModel绑定到 QTreeView、 QListView、QTableView

QFileSystemModel绑定到 QTreeView、 QListView、QTableView&#xff0c;实现文件的查看 .h文件 #ifndef FILESYSEXAMPLE_H #define FILESYSEXAMPLE_H#include <QMainWindow> #include <QFileSystemModel>namespace Ui { class FileSysExample; }class FileSysExam…

Vuex的模块化编程

1.之前我们使用store引入的时候不够简介&#xff0c;store为我们封装了方法 mapState:从state中获取数据,以数组的方式返回 mapGetters:从getters中获取方法,以数组的方式返回 mapMutations:从mutations中获取操作,以数组的方式返回 mapActions:从actions中获取动作,以数组的方…

Hi3861 OpenHarmony嵌入式应用入门--MQTT

MQTT 是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输 协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用&#xff0c;是专为受限设备和低带宽、 高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器…

PFC电路中MOS管的选取2

上面这种驱动方式叫推挽驱动&#xff0c;或者图腾柱驱动 当芯片驱动脚 DRV为高电平时&#xff0c;此时回路中的源是芯片的 DRV引脚&#xff0c;芯片驱动电流从左往右流动&#xff0c;通过 R1&#xff0c;通过Q1的be脚&#xff0c;通过R3、R4给MOS管Q4的Cgs结电容充电 不过值得…

Mybatis-Plus一文详解BaseMapper和Service 使用

Mybatis-Plus简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性&#xff1a; 无侵入&#xff1a;只做增强不做…

idea 默认路径修改

1.查看 idea 的安装路径&#xff08;右键点击 idea 图标&#xff0c;查看路径 &#xff09; “C:\Program Files\JetBrains\IntelliJ IDEA 2021.3.1\bin\idea64.exe” 在 bin 目录查看 idea.properties 文件&#xff0c;修改以下四个路径文件 # idea.config.path${user.home}/…

Linux的前世今生

Unix的起源和发展 1969年&#xff0c;AT&T贝尔实验室的Ken Thompson和Dennis Ritchie等人开发了Unix操作系统。Unix的设计理念强调小而简洁的工具&#xff0c;文本流和系统模块化&#xff0c;这些理念后来成为Linux开发的重要基础。1973年&#xff0c;Unix用C语言重新编写…

昇思25天学习打卡营第17天 | K近邻算法实现红酒聚类

内容介绍&#xff1a; K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;是机器学习最基础的算法之一。它正是基于以上思想&#xff1a;要确定一个样本的类别&#xff0c;可以计算它与所有训练样本的距离&#xff0…

LVGL移植与VS模拟器使用

一、移植文件介绍 二、移植部分 第一步&#xff1a;创建LVGL文件夹 第二步&#xff1a; 构造LVGL文件夹&#xff1a;LVGL - GUI - lvgl - 第三步&#xff1a;添加文件 3.1 从examples中添加2个.c文件 3.2 从src中添加文件 draw文件 extra文件 第四步&#xff1a; 三、Ke…

分享2个iPhone恢复照片的方法,赶紧码住收藏!

手机已经成为我们生活中不可或缺的一部分&#xff0c;它不仅仅是通讯工具&#xff0c;更是我们记录生活点滴的重要工具之一。然而&#xff0c;正如其他任何设备一样&#xff0c;iPhone上存储的照片有时也会不小心被删除或丢失。 别担心&#xff0c;即使你误删了重要的照片&…

网安加·百家讲坛 | 马云卓:漏洞扫描工具漏洞报告对比

作者简介&#xff1a;马云卓&#xff0c;某安全公司安全专家&#xff0c;持有注册信息安全专业人员及渗透测试工程师&#xff08;CISP-PTE&#xff09;和项目管理专业人士&#xff08;PMP&#xff09;证书&#xff0c;拥有丰富的行业经验&#xff0c;长期专注于网络安全攻防技术…

用SOLIDWORKS批量打印工程图纸,没有难度

在工程师完成产品设计后&#xff0c;一般需要打印纸质工程图&#xff0c;如果打印的数量比较多&#xff0c;效率就会比较低&#xff0c;其实SOLIDWORKS软件提供了专用工具用来处理工作量比较大且重复性的工作&#xff0c;这个工具就是SOLIDWORKS Task Scheduler。 SOLIDWORKS T…

css实现鼠标禁用(鼠标滑过显示红色禁止符号)

css实现鼠标禁用&#xff08;鼠标滑过显示红色禁止符号&#xff09; 创作背景css鼠标禁用 创作背景 从本文开始&#xff0c;将会用三篇文章来一步一步实现 vueantdts实战后台管理系统中table表格的不可控操作。中间会补充两篇css知识文章 &#xff0c;方便后续功能的实现。 实…

面向对象编程:定义、特点、应用场景、优缺点及示例代码

目录 前言1. 面向对象编程的定义2. 面向对象编程的特点2.1 封装2.2 继承2.3 多态2.4 抽象 3. 面向对象编程的应用场景3.1 大型软件系统3.2 GUI应用程序3.3 游戏开发 4. 面向对象编程的优缺点4.1 优点4.2 缺点 5. 代表性的编程语言5.1 Java5.2 C5.3 Python 6. 示例代码结语 前言…