南通网站设计制作/知乎推广
C++11的future和async等关键字
1.async和future的概念
std::async
和 std::future
是 C++11 引入的标准库功能,用于实现异步编程,使得在多线程环境中更容易处理并行任务。它们可以帮助你在不同线程中执行函数,并且能够方便地获取函数的结果。
在之前使用线程的时候,我们没有办法很好的获取到线程所执行函数的返回值。甚至更多时候,我们使用线程执行的都是不关心返回值的函数。如果真的想要获取线程函数的返回值,可以将一个指针作为输出型参数放入线程所执行的函数中。主执行流执行t.join()
等待线程执行结束,并获取到这个返回值。
但这样并不是非常方便。于是C++11就引入了如上两个关键字来帮助我们获取到线程所执行函数的返回值。适用于异步执行某些耗时的函数,提高程序运行的效率:
- 异步执行耗时函数
- 主执行流干其他事情
- 通过
std::future
获取到返回值 - 继续向后执行
基本的并行概念在多线程部分都已经讲过了,这里就不多bb,直接上代码吧!
2.使用
2.1 std::launch
在使用std::async
之前,还需要认识一个枚举类型 launch,在使用std::async
的函数传参的时候会用到(这里先说一下,std::async
是用来帮我们创建线程的)
enum class launch; // std::launch
在 cplusplus
网站上,有这个枚举类型的释义,这里面只有俩值
说一下这俩值的区别
launch::async
,立即创建一个线程来执行目标函数launch::deferred
,不立即创建线程,而是等待调用std::future
的get()
函数时才调用(这个get函数是用来获取返回值的)
好了知道这个就够了哈!
2.2 std::result_of
这里还出现了另外一个关键字,就顺带也说说是干嘛的(其实我自己也不知道,现学现卖)
#include <type_traits> // 头文件template <class Fn>
struct result_of;template <class Fn, class... ArgTypes>
struct result_of<Fn(ArgTypes...)>;
以下是 std::result_of
的基本用法和概念:
- 使用
std::result_of
获取函数调用的返回类型: 你可以通过将函数类型和参数类型传递给std::result_of
来推导函数调用的返回类型。这使得你可以在编译时获取函数调用的结果类型,而不需要手动指定它。 - 用法示例: 假设有一个函数
int add(int a, int b)
,你可以使用std::result_of
来获取该函数在给定参数下的返回类型。
#include <iostream>
#include <type_traits>int add(int a, int b)
{return a + b;
}int main()
{std::result_of<decltype(add) &(int, int)>::type result = add(3, 5);std::cout << "Result: " << result << std::endl;return 0;
}
运行结果如下
$ g++ test.cpp -o test -std=c++11
$ ./test
Result: 8
需要注意的是,使用decltype
关键字来指定函数指针的时候,函数名和函数参数之间需要加上&
,否则无法正确推导类型
decltype(add) &(int, int) // 正确
decltype(add) (int, int) // 错误
2.3 async
先来看看async函数的样本,第一个情况是不显示传入 std::launch
,第二个函数重载是传入了std::launch
作为启动策略
#include <future> // 头文件// unspecified policy (1)
template <class Fn, class... Args>future<typename result_of<Fn(Args...)>::type>async (Fn&& fn, Args&&... args);
// specific policy (2)
template <class Fn, class... Args>future<typename result_of<Fn(Args...)>::type>async (launch policy, Fn&& fn, Args&&... args);
在cplusplus网站上,说到了第一种情况是由编译器自主决定到底是采用 std::launch::async
或 std::launch::deferred
,这就需要根据平台和编译器实现以及调用逻辑的不同来具体分析了。所以不建议使用第一个,还是直接指定launch policy
(翻译过来是启动策略)的会好一点。
所以只看第二个👇
template <class Fn, class... Args>future<typename result_of<Fn(Args...)>::type>async (launch policy, Fn&& fn, Args&&... args);
这里采用了可变模板参数来接收多个函数参数,类似于可变参数列表。这里还使用了typename
关键字来告知编译器result_of<Fn(Args...)>::type
是一个参数类型,需要在模板实例化了之后再去获取确定的类型。而class Fn
是一个函数指针的模板变量。
- 第一个参数是
std::launch
,上文已经提到过两个不同选项的区别了 - 第二个参数是函数,直接丢函数名就可以了
- 第三个参数是这个函数的参数,也是直接丢参数就可以了
如下是一个简单的调用示例(并非完整示例)
#include <future> // 头文件
// 函数
int add(int a, int b) {return a + b;
}
// 调用
std::async(std::launch::async, add, 3, 5);
调用了这个函数后,CPP会帮我们创建一个线程来执行函数,并根据第一个启动参数的不同,决定啥时候创建这个线程。最终我们可以通过future
获取到线程执行函数的返回值。
2.4 future
人如其名,这个类型是用来声明一个未来
的变量的。因为std::async
会帮我们创建一个线程来执行函数,此时该线程函数的返回值是未知的,这个未来变量就是提前的一个声明,当线程执行完毕函数并返回值的时候,这个变量的值才真正被初始化为我们真正需要的那个值。
template <class T> future;
// specialization : T is a reference type (R&)
template <class R&> future<R&>;
// specialization : T is void
template <> future<void>;
其有如下几个成员函数
- get:获取对应async所执行函数的返回值,如果函数没有执行完毕则阻塞等待
- valid:bool,判断当前future类型到底有没有和一个async函数所对应
- share:将
future
对象转成一个std::shared_future
对象 - wait:等待异步任务完成,但不获取结果
- wait_for:等待异步任务完成,但有等待的时长(没等到就返回错误)
- wait_until:等待异步任务完成,直到一个确定的时间(没等到就返回错误)
后面三个wait函数和CPP线程中的wait函数如出一辙。
2.5 share_future
share_future
就好比share_ptr
智能指针,其让future
对象从单一所有权变成了多人可用。本来是一个只能坐一人的餐桌,现在变成了可以坐很多人的大桌子。
template <class T> shared_future;
template <class R&> shared_future<R&>; // specialization : T is a reference type (R&)
template <> shared_future<void>; // specialization : T is void
成员函数和future
完全一样(只不过么有share()
函数)这里就不赘述了;
future
是单人餐桌,一次只能有一个线程执行get函数;当get被执行后,这个future会失效。share_future
是大桌子,所有人一起坐在这个桌子上等服务员上菜,互不干扰;
2.5 测试
2.5.1 正常测试
如下代码是一个简单的使用示例,并且通过提供不同的std::launch
启动策略,我们也能观察到不同的现象
#include <iostream>
#include <future>
#include <type_traits> // result_of
#include <sys/unistd.h> //sleepint add(int a, int b)
{std::cout << "Add Thread " << std::this_thread::get_id() << " | Sleeping before add..." << std::endl;sleep(4);return a + b;
}int main()
{std::cout << "Main Thread " << std::this_thread::get_id() << " | Start" << std::endl;//std::future<int> futureResult = std::async(std::launch::deferred, add, 3, 5); // 不会创建新线程std::future<int> futureResult = std::async(std::launch::async, add, 3, 5); // 创建新线程sleep(3); std::cout << "Main Thread " << std::this_thread::get_id() << " | Waiting for result..." << std::endl;int result = futureResult.get(); // 等待异步任务完成并获取结果std::cout << "Main Thread " << std::this_thread::get_id() << " | Result: " << result << std::endl;sleep(3);return 0;
}
如果使用std::launch::async
作为启动策略,可以看到,执行add函数的线程id和主线程的id是不同的,通过linux下的ps -aL
命令也能观察到出现两个线程
while :; do ps jax | head -1 && ps -aL | grep -v grep;sleep 1; echo "########################"; done
程序执行输出结果
Main Thread 139869475694400 | Start
Add Thread 139869457655552 | Sleeping before add...
Main Thread 139869475694400 | Waiting for result...
Main Thread 139869475694400 | Result: 8
但如果使用std::launch::deferred
作为启动策略,则会发现这两个线程的id是完全相同的,这代表实际上其执行了并行的策略
Main Thread 139789724776256 | Start
Main Thread 139789724776256 | Waiting for result...
Add Thread 139789724776256 | Sleeping before add...
Main Thread 139789724776256 | Result: 8
2.5.2 多线程get一个future
在如下代码中,我写了一个void future_get_func(std::future<int>& fu)
的函数,尝试开一个线程来get,然后主执行流又get一次,看看会发生什么。
#include <iostream>
#include <future>
#include <functional>
#include <thread>
#include <type_traits> // result_of
#include <sys/unistd.h> // sleepint add(int a, int b)
{std::cout << "Add Thread " << std::this_thread::get_id() << " | Sleeping before add..." << std::endl;sleep(4);return a + b;
}void future_get_func_shared(std::shared_future<int>& fu)
{int result = fu.get(); // 等待异步任务完成并获取结果std::cout << "Func Thread " << std::this_thread::get_id() << " | Result: " << result << std::endl;sleep(2);
}void future_get_func(std::future<int>& fu)
{int result = fu.get(); // 等待异步任务完成并获取结果std::cout << "Func Thread " << std::this_thread::get_id() << " | Result: " << result << std::endl;sleep(2);
}int main()
{std::cout << "Main Thread " << std::this_thread::get_id() << " | Start" << std::endl;//std::future<int> futureResult = std::async(std::launch::deferred, add, 3, 5); // 不会创建新线程std::future<int> futureResult = std::async(std::launch::async, add, 3, 5); // 创建新线程sleep(3); std::cout << "Main Thread " << std::this_thread::get_id() << " | Waiting for result..." << std::endl;// 尝试测试多线程get会发生什么std::thread t1(future_get_func, std::ref(futureResult)); // 开个线程来gett1.detach(); // 直接分离线程sleep(1);int result = futureResult.get(); // 等待异步任务完成并获取结果std::cout << "Main Thread " << std::this_thread::get_id() << " | Result: " << result << std::endl;sleep(3);return 0;
}
需要注意的是,如下创建线程的传参必须要用std::ref
包裹,来告知线程这是一个引用对象,否则编译会报错。因为 std::thread
要求参数可以在构造函数中被调用,而 std::future
并不能直接传递给 std::thread
;
std::thread t1(future_get_func, std::ref(futureResult)); //正确
std::thread t1(future_get_func, futureResult); //错误
编译通过后执行,会发现跑出来了一个std::future_error
异常,代表我们在一个无效的future
上调用了get
函数。
Main Thread 139869577889600 | Start
Add Thread 139869559850752 | Sleeping before add...
Main Thread 139869577889600 | Waiting for result...
Func Thread 139869551458048 | Result: 8
terminate called after throwing an instance of 'std::future_error'what(): std::future_error: No associated state
Aborted
记住了,std::future
在调用了一次get
之后将不再与对应的std::async
关联,所以才会需要share_future
的出现!
改成share_future
再执行上面这套逻辑,就会发现成功跑起来了
Main Thread 140369883957056 | Start
Add Thread 140369865918208 | Sleeping before add...
Main Thread 140369883957056 | Waiting for result...
Func Thread 140369857525504 | Result: 8
Main Thread 140369883957056 | Result: 8
2.5.3 异常处理
int add(int a, int b)
{std::cout << "Add Thread " << std::this_thread::get_id() << " | Sleeping before add..." << std::endl;sleep(4);throw std::runtime_error("An error occurred");return a + b;
}
如果async
执行的函数中抛出了异常,那么这个异常将会被传回主执行流,可以在主执行流中被处理。而如果直接使用线程来执行这个函数,其异常不会被捕捉,而是会导致整个进程退出。
下图,使用async运行,成功打印出异常(捕获成功)
下图,使用线程运行,进程退出
2.6 launch::deferred的真正意义
如果std::launch::deferred
是同步执行,这样写不是多此一举吗?
NONONO 非也非也,和直接调用Add
函数相比,用这样的方式调用Add
函数还是有些区别的:
- 推迟执行:直接调用Add函数是立马执行,但是用async可以推迟到调用get的时候才执行
- 延迟计算:有的时候我们并不是需要立马使用这个函数的返回值,所以就可以延迟一会再执行这个函数,先把函数的调用搞起来,后面只需要一个get就能获取到结果了
- 避免线程创建:并不是什么时候多线程都更好,有些时候创建一个线程的消耗还不如直接执行函数来的快(比如函数干的活很小的情况)
所以,这个关键字多少还是有点作用了。
2.7 future_error/errc/status
除了future
和share_future
,还有如下几个类型
std::future_error
:std::future_error
是一个异常类,用于表示与std::future
相关的错误。当在使用std::future
时出现错误,例如获取结果时异步任务抛出了异常,就会抛出std::future_error
异常。它是一个标准异常类型,通常通过捕获异常对象来处理异步任务执行过程中的问题。std::future_errc
:std::future_errc
是一个枚举类型,用于表示std::future_error
中的不同错误情况。这样的枚举类型是为了在处理异常时更加明确和方便。它包含了一系列可能的错误,如broken_promise
(promise 被破坏,即 promise 对象的 set_value 或 set_exception 被多次调用)和future_already_retrieved
(future 对象已经被获取过一次)等。std::future_status
:std::future_status
是一个枚举类型,用于表示std::future
的状态。它描述了一个std::future
对象的当前情况,指示异步任务是否已完成、是否有效等。std::future_status
包含三个值:ready
(异步任务已完成,可以获取结果)、timeout
(等待超时,即异步任务还未完成)、deferred
(异步任务延迟执行)。
3.为什么C++会出现futrue?
为啥要出一个future?直接用老办法不也可以这么玩吗?
C++ 标准库引入 std::future
和相关的异步编程机制,是为了更好地支持并发编程和多线程环境。这些机制的出现有几个原因和动机:
- 并发性和性能提升: 在现代计算机体系结构中,多核处理器已经成为常态。为了充分利用这些多核资源,编写并发代码变得重要。
std::future
提供了一种方式,可以在多个线程中同时执行任务,并且可以方便地获取任务的结果,从而允许程序在多核处理器上并行执行,提高性能。 - 任务分离: 在很多情况下,我们希望将一个大的任务拆分成多个子任务,在不同的线程中并行执行,然后合并子任务的结果。
std::future
允许你在一个线程中等待另一个线程的任务完成,从而支持这种任务分离和并行执行的模式。 - 避免阻塞: 在传统的同步编程中,如果某个操作需要等待,会导致线程阻塞。而异步编程机制允许线程继续执行其他操作,而不必等待一个潜在的耗时操作完成。
std::future
允许你在一个线程中发起异步操作,并在需要的时候获取操作的结果,从而避免了不必要的阻塞。 - 异常处理: 在多线程环境中,处理异步任务的异常可能变得复杂,因为异步任务在不同的线程中执行。
std::future
引入了异常传递机制,允许异步任务在执行过程中抛出异常,并将这些异常传递到等待结果的线程中。
总之,C++ 的 std::future
和相关的异步编程机制提供了一种更高级别、更方便的方式来处理多线程并发编程。这些机制使得开发者能够更容易地利用多核处理器的性能,并更灵活地设计并发代码,从而在面对并发和异步任务时能够更好地管理资源、提高效率和处理异常。
4.promise
4.1 概念
std::promise
是 C++ 标准库中用于在一个线程中产生结果,然后在另一个线程中获取结果的工具。它提供了一些成员函数来设置结果、处理异常以及获取关联的 std::future
对象等。
template <class T> promise;
template <class R&> promise<R&>; // specialization : T is a reference type (R&)
template <> promise<void>; // specialization : T is void
下面是一些常用的 std::promise
成员函数及其用法:
-
set_value
: 用于设置结果值。如果你已经通过get_future()
获取了一个std::future
对象,调用set_value
将会使等待结果的线程被唤醒并获取结果。std::promise<int> promiseObj; std::future<int> futureResult = promiseObj.get_future();// 在某个线程中设置结果值 promiseObj.set_value(42);
-
set_exception
: 用于设置异常,将在等待结果的线程中抛出。这允许你在产生结果的线程中处理异常情况。try {// 产生异常throw std::runtime_error("An error occurred"); } catch (...) {// 将异常设置到 promise 对象中promiseObj.set_exception(std::current_exception()); }
-
get_future
: 返回与std::promise
关联的std::future
对象。通过这个std::future
,你可以在另一个线程中等待并获取结果。std::promise<int> promiseObj; std::future<int> futureResult = promiseObj.get_future();
-
swap
: 交换两个std::promise
对象的状态,包括关联的std::future
对象和设置的结果。std::promise<int> promise1; std::promise<int> promise2;// 交换两个 promise 对象的状态 promise1.swap(promise2);
-
valid
: 检查std::promise
对象是否有效,即是否与一个std::future
对象关联。std::promise<int> promiseObj;if (promiseObj.valid()) {// promiseObj 有效 } else {// promiseObj 无效 }
还有下面这俩个成员函数,看名字也能猜出来它是干嘛的,就不多说了
-
set_value_at_thread_exit
Set value at thread exit (public member function )
-
set_exception_at_thread_exit
Set exception at thread exit (public member function )
这些成员函数允许你在一个线程中产生结果或异常,并在另一个线程中等待和处理这些结果或异常。它们为多线程编程提供了一种可靠的方式来传递数据和控制流。请注意,在使用 std::promise
时,你需要仔细处理异常和线程同步,以确保正确的结果传递。
4.2 示例
然后下头是一个基本的使用示例;你可以理解为promise
就是一个用来承担线程所执行函数的参数和异常的一个变量,我们可以通过set_value
并在主执行流中使用future.get
来获取到这个值,也可以设置异常,并在主执行流中处理这个异常;
不过promise
和future
一样,是一次性的,设置value和异常都只能设置一次,设置完毕后就不能再设置了
#include <iostream>
#include <future>void worker(std::promise<int>& p)
{p.set_value(42); // 设置值
}int main()
{std::promise<int> promiseObj;std::future<int> futureResult = promiseObj.get_future();std::thread t(worker, std::ref(promiseObj)); // 通过线程执行t.join();int result = futureResult.get(); // 主线程中获取值std::cout << "Result: " << result << std::endl;return 0;
}
5.packaged_task
std::packaged_task
是 C++ 标准库中的一个类模板,用于将一个可调用对象(函数、函数对象或可调用成员函数)封装成一个可以异步执行的任务,并且可以通过 std::future
获取任务的返回值。它在多线程编程中起到了连接异步任务和线程间通信的桥梁作用。
std::packaged_task
的主要作用有以下几个方面:
- 封装任务:
std::packaged_task
允许你将一个可调用对象封装成一个任务,这个任务可以在另一个线程中异步执行。你可以将函数、函数对象或可调用成员函数封装为一个std::packaged_task
实例。 - 异步执行: 通过将
std::packaged_task
实例传递给一个std::thread
或其他支持异步执行的机制,你可以在新的线程中执行封装的任务,而不需要显式创建线程函数。 - 获取返回值:
std::packaged_task
可以与std::future
一起使用,以获取异步任务的返回值。你可以通过packaged_task
的get_future
方法获取一个与任务关联的std::future
对象,然后在适当的时候使用std::future
的get
方法来获取任务的返回值。 - 线程池:
std::packaged_task
结合线程池的使用,可以更灵活地控制任务的执行方式。线程池可以预先创建一组线程,然后将封装好的任务分配给这些线程执行,避免了频繁创建和销毁线程的开销。
5.1 示例
下面是一个简单示例,演示了如何使用 std::packaged_task
来异步执行一个函数并获取其返回值
#include <iostream>
#include <thread>
#include <future>int add(int a, int b)
{return a + b;
}int main()
{std::packaged_task<int(int, int)> task(add);std::future<int> future = task.get_future();std::thread worker(std::move(task), 3, 5);worker.join(); // 通过线程异步执行这个taskint result = future.get(); // 等待返回值std::cout << "Result: " << result << std::endl;return 0;
}
在这个例子中,std::packaged_task
封装了一个函数 add
,然后通过 std::thread
异步执行,最后通过 std::future
获取异步任务的返回值。
5.2 make_ready_at_thread_exit
packaged_task
的成员函数中,主要还是这个需要单独说明;先看如下代码
#include <iostream>
#include <thread>
#include <future>int main()
{std::packaged_task<int()> task([](){ return 42; });std::future<int> future = task.get_future();std::thread worker([&task]() {// 模拟一些工作std::this_thread::sleep_for(std::chrono::seconds(2));// 设置任务结果值task.make_ready_at_thread_exit();});worker.detach(); // 分离线程,不等待其结束// 等待任务结果int result = future.get();std::cout << "Result: " << result << std::endl;return 0;
}
我们让packaged_task
执行的是一个return 42
的函数,而线程里面还会进行其他处理。
而make_ready_at_thread_exit()
的作用,就是确认当前的worker
线程已经干完自己的活了,可以执行packaged_task
封装的函数了!
相当于是一个确认packaged_task
中封装的任务到底在什么时候执行的一个函数。调用这个函数的时候,就会开始执行其包装的异步函数,并返回结果给future.get()
的执行流
The end
收工
相关文章:

【C++11】future和async等
C11的future和async等关键字 1.async和future的概念 std::async 和 std::future 是 C11 引入的标准库功能,用于实现异步编程,使得在多线程环境中更容易处理并行任务。它们可以帮助你在不同线程中执行函数,并且能够方便地获取函数的结果。 在…...

Linux 系统下 GDB 调试器的使用
文章目录 简介GDB 的介绍GDB 的使用 GDB 常用命令及示例查看相关操作断点相关操作运行相关操作变量相关操作分隔窗口操作 简介 GDB 的介绍 GDB 是 GNU 调试程序,是用来调试 C 和 C 程序的调试器。它可以让程序开发者在程序运行时观察程序的内部结构和内存的使用情况…...

个人首次使用UniAPP使用注意事项以及踩坑
个人首次使用UniAPP 使用注意事项以及踩坑 自我记录 持续更新 1.vscode 插件 uni-create-view 快速nui-app页面的 uni-helper uni-app代码提示的 uniapp小程序扩展 鼠标悬停查文档 Error Lens 行内提示报错 "types": ["dcloudio/types", "mini…...

VSCode 如何解决 scanf 的输入问题——Code is already running!
文章如何使用 VSCode 软件运行C代码中已经介绍了如何在 VSCode 软件中运行C代码,但最近在使用 scanf 想从键盘输入时,运行代码后显示“Code is already running!”,如下图所示,在输出窗口是无法通过键盘输入的。 解决办法如下&am…...

短视频seo源码矩阵系统开源---代码php分享
前言:短视频seo源码 短视频seo矩阵系统源码私有化部署 短视频seo源码 短视频seo矩阵系统源码私有化怎么部署? 首先我们来给大家普及一下什么是短视频seo矩阵系统?视频矩阵分为多平台矩阵与一个平台多账号矩阵,加上seo排名优化&…...

【docker】中文无法显示输入等问题解决方法
every blog every motto: You can do more than you think. csdn: https://blog.csdn.net/weixin_39190382?typeblog ID: 胡侃有料 0. 前言 docker 路径中文不显示,无法输入中文问题解决方法 1. 解决方法 1.1 临时解决 打开etc/profile文件,末尾添…...

leetcode 1035. 不相交的线
2023.8.25 本题可以转化为:求两数组的最长公共子序列。 进而可以用dp算法解决。 方法类似于这题最长公共子序列 。 代码如下: class Solution { public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {vector<…...

Hystrix: 服务降级
cloud是基础,eureka是服务注册和发现,consumer是消费者去消费provider里的东西,消费方式就是Feign和Ribbon,feign 接口消费,ribbon Rest消费 服务降级发生在客户端,客户端因为请求关闭的服务器࿰…...

高精度运算(加减乘除乘法)
所谓高精度,就是大数的运算,这个大数可能是要远远超过现有数据类型的最大范围。如果我们想进行这样的运算,就要掌握计算的原理——竖式运算。 加法 我们这里先简单考虑非负数的加法,竖式这么列对吧: ①存储 我们如何…...

Mysql数据库技术知识整理
Mysql的知识点目录 重点:架构,引擎,索引,锁机制,事务机制,日志机制,集群,调优 3、Mysql索引 索引概念 覆盖索引: 条件列和结果列都在索引中索引下推: 查询会先过滤条件列,然后回表查数据最左前缀匹配&am…...

SpringBoot整合Mybatis 简单试用
1. 导入依赖 我使用MySQL,需要导入MySQL的驱动依赖此外要在SpringBoot中使用Mybatis,则需要导入Mybatis启动器 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifact…...

SpringBoot案例-配置文件-yml配置文件
配置格式 SpringBoot提供了多种属性配置方式 application.propertiesapplication.ymlapplication.yaml常见配置文件格式对比 XML(臃肿) <configuration><database><host>localhost</host><port>3306</port><use…...

Web Components
Web Components标准非常重要的一个特性是,它使开发者能够将HTML页面的功能封装为custom elements(自定义标签),可以使用CustomElementRegistry来管理自定义标签 <script>//1、创建自定义标签class NewElement extends HTML…...

IT运维软件的费用是多少?
正常一套IT运维软件费用一般在5千-50万之间不等,而且分为一次性付费或年付费模式,付费方式导致的价格也不同。 正常情况下IT运维软件的具体价格,是需要根据企业的实际需求来进行综合评估,一般来说,影响具体价格费用有以…...

基于Three.js的WebXR渲染入门
1、Three.js 渲染管线快速概览 我不会花太多时间讨论 Three.JS 渲染管道的工作原理,因为它在互联网上有详细记录(例如,此链接)。 我将在下图中列出基础知识,以便更容易理解各个部分的去向。 2、WebXR 设备 API 入门 在我们深入了解 WebXR API 本身之前,您应该知道 WebX…...

resource doesn‘t have a corresponding Go package.
resource doesnt have a corresponding Go package. GO这个鬼东西不能直接放src下。 ************ Building Go project: ProjectGoTest ************with GOPATH: D:\Go;D:\eclipse-jee-oxygen-2-win32-x86_64\workspace\ProjectGoTest >> Running: D:\Go\bin\go.exe …...

【微服务】微服务调用原理及服务治理
本文通过图文结合,简要讲述微服务的调用原理,以及服务治理的相关概念。 1.微服务的调用原理 举个栗子:你去会所洗脚。首先,技师肯定要先去会所应聘,通过之后,会所会记录该技师的信息和技能,然后…...

【在Windows下搭建Tomcat HTTP服务】
文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器,不仅名字很有趣࿰…...

前端Vue3框架知识点大全
Vue.js是一种流行的JavaScript前端框架,它的第三个版本Vue3带来了许多令人兴奋的新特性和改进。 1、响应式数据: Vue 3采用了基于Proxy的响应式系统,相比Vue 2中的Object.defineProperty,Proxy提供了更强大和灵活的拦截器&#…...

C语言练习2(巩固提升)
C语言练习2 选择题 前言 “志之所趋,无远弗届,穷山距海,不能限也。”对想做爱做的事要敢试敢为,努力从无到有、从小到大,把理想变为现实。要敢于做先锋,而不做过客、当看客,让创新成为青春远航的…...

Vulnhub: DriftingBlues: 1靶机
kali:192.168.111.111 靶机:192.168.111.215 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.215 80端口首页源码 访问noteforkingfish.txt,发现为Ook!加密的密文 解密后提示需要用户eric和修改hosts文件&…...

Android项目如何上传Gitee仓库
前言 最近Android项目比较多,我都是把Android项目上传到Gitee中去,GitHub的话我用的少,可能我还是更喜欢Gitee吧,毕竟Gitee仓库用起来更加方便 一. 创建Gitee仓库 1. 先创建一个Gitee账号,然后登录上去 2. 创建Androi…...

MySQL——基础——联合查询
联合查询 - union,union all 对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集 SELECT 字段列表 FROM 表A ... UNION [ALL] SELECT 字段列表 FROM 表B ...; 1.将薪资低于5000的员工,和 年龄大于50的员工全部查询出来 use itcast; select * from participat…...

Vue3+Vite 初始化Cesium
Vue3Vite 初始化Cesium 安装依赖 yarn add cesium yarn add vite-plugin-cesium -D加载vite-plugin-cesium插件 import { defineConfig } from vite import vue from vitejs/plugin-vue import cesium from vite-plugin-cesium;export default defineConfig({plugins: [vue(…...

c++内存地址分配
...

改进YOLO系列:9.添加S2Attention注意力机制
添加S2Attention注意力机制 1. S2Attention注意力机制论文2. S2Attention注意力机制原理3. S2Attention注意力机制的配置3.1common.py配置3.2yolo.py配置3.3yaml文件配置1. S2Attention注意力机制论文 论文题目:S 2 -MLPV2: IMPROVED SPATIAL-SHIFT MLP ARCHITECTURE…...

微服务Feign组件远程调用自定义解码器
Feign远程调用响应结果格式 public class Result<T> {/*** 响应码,200为成功*/private Integer code;/*** 响应信息*/private String message;/*** 响应的具体对象*/private T data; }自定义Feign解码器 Component // 注入Spring的IOC容器中,所有…...

FairyGUI编辑器自定义菜单扩展插件
本文涉及到的软件有:FairyGUI,VSCode 代码环境涉及到了:Lua VSCode插件:EmmyLua 在编写FairyGUI编辑器菜单前,了解一下FairyGUIEditor的API会有效的帮助我们解决很多问题。FairyGUI的扩展是通过编辑器自带的插件功能…...

若依二次开发
目录 本地启动 前端代码 后端代码 代码适配 数据表初始化 远程部署...

安全(权限)框架Shiro概述及原理
1.1 Shiro是什么 Apache Shiro是一个功能强大且易于使用的Java安全(权限)框架。Shiro可以完成:认证、授权、加密、会话管理、与Web集成、缓存 等。借助Shiro您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的Web和企业应用…...