C++并发编程:std::future、std::async、std::packaged_task与std::promise的深度探索
C++并发编程:std::future、std::async、std::packaged_task与std::promise的深度探索
- 一、引言 (Introduction)
- 1.1 并发编程的概念 (Concept of Concurrent Programming)
- 1.2 C++并发编程的重要性 (Importance of Concurrent Programming in C++)
- 1.3 关于std::future、std::async、std::packaged\_task和std::promise的简介 (Introduction to std::future, std::async, std::packaged\_task, and std::promise)
- 二、std::future:异步结果的储存与获取
- 2.1 std::future的基本原理和结构
- 基本原理
- 结构与方法
- 2.2 std::future的使用场景和示例代码
- 使用 std::async 启动异步任务
- 使用 std::packaged\_task 包装可调用对象
- 使用 std::promise 显式设置异步操作的结果
- 2.3 std::future在高级应用中的应用
- 异步操作链
- 异步数据流管道
- 异步任务之间的依赖关系
- 在超时后取消异步任务
- 三、std::async:异步任务的启动与管理 (std::async: Launching and Managing Asynchronous Tasks)
- 3.1 std::async的基本原理和结构 (Basic Principles and Structure of std::async)
- 3.2 std::async的使用场景和示例代码 (Use Cases and Example Code for std::async)
- 3.2.1 基本的异步任务
- 3.2.2 异步执行模式和延迟执行模式
- 3.2.3 错误处理
- 3.3 std::async在高级应用中的应用 (Applications of std::async in Advanced Use Cases)
- 3.3.1 并行算法
- 3.3.2 后台任务
- 3.3.3 异步日志系统
- 3.3.4 实时计算系统
- 四、std::packaged\_task:封装可调用目标的功能
- 4.1 std::packaged\_task的基本原理和结构
- 基本原理
- 结构
- 4.2 std::packaged\_task的使用场景和示例代码
- 4.3 std::packaged\_task在高级应用中的应用
- 任务队列
- 线程池
- 异步任务链
- 五、std::promise:异步操作的结果承诺 (std::promise: Promise of Asynchronous Operation Results)
- 5.1 std::promise的基本原理和结构 (Basic Principles and Structure of std::promise)
- 基本原理
- 结构
- 5.2 std::promise的使用场景和示例代码 (Use Cases and Example Code for std::promise)
- 使用场景
- 示例代码
- 5.3 std::promise在高级应用中的应用 (Applications of std::promise in Advanced Use Cases)
- 高级应用一:链式异步任务
- 高级应用二:与其它并发工具结合使用
- 六、并行类和线程池
- 并行库
- 选择权衡
- 自定义线程池
- 并行库对线程池的帮助
- 1. std::thread
- 2. std::future和std::promise
- 3. std::async
- 4. std::packaged\_task
- 并行库与线程池结合
一、引言 (Introduction)
1.1 并发编程的概念 (Concept of Concurrent Programming)
并发编程是一种计算机编程技术,其核心在于使程序能够处理多个任务同时进行。在单核处理器上,虽然任何给定的时间只能运行一个任务,但通过任务切换,可以创建出并发执行的效果。而在多核处理器上,可以真正同时处理多个任务。
并发编程的目标是提高程序执行的效率。特别是在处理大量数据或需要进行大量计算的情况下,通过并发执行,可以显著提高程序的运行速度。同时,也可以提高程序的响应性,即使一部分任务在运行过程中出现阻塞,也不会影响到其他任务的执行。
并发编程并不是一项简单的任务,它涉及到许多复杂的问题,如数据同步、资源共享和死锁等。因此,需要深入理解并发编程的各种概念和技术,才能编写出正确和高效的并发程序。
C++作为一种广泛使用的编程语言,提供了一套完善的并发编程库。这些库包括多线程支持、同步工具(如互斥锁和条件变量)以及在本文中将深入探讨的std::future、std::async、std::packaged_task和std::promise等工具。
这些工具提供了强大的功能,可以帮助我们更方便地编写并发程序。然而,要充分利用它们,就需要深入理解它们的工作原理和使用方法。本文旨在帮助读者理解并掌握这些复杂但强大的工具。
下面,让我们开始深入探索C++的并发编程世界吧。
1.2 C++并发编程的重要性 (Importance of Concurrent Programming in C++)
在当今的计算机环境中,处理器的核心数量正在迅速增长,使得并发编程变得越来越重要。对于许多应用程序来说,如果不能充分利用多核处理器的并行计算能力,那么它们就无法实现其潜在的性能。
并发编程在C++中具有特别重要的地位。C++是一种多范式编程语言,它支持过程式编程、面向对象编程,以及泛型编程等多种编程方式。并且,C++还提供了一套强大的并发编程库,使得我们可以编写出高效并且可扩展的并发程序。
C++并发编程的重要性体现在以下几个方面:
- 性能提升:通过利用多核处理器的并行计算能力,我们可以编写出更高效的程序。在处理大量数据或需要进行大量计算的情况下,这种性能提升会非常明显。
- 提高响应性:在许多应用程序中,我们需要在处理长时间运行的任务时,还能保持对用户输入的响应。通过并发编程,我们可以在一个线程中运行长时间的任务,而在另一个线程中处理用户输入,从而提高程序的响应性。
- 利用现代硬件:随着处理器核心数量的增加,未来的程序必须能够处理并行和并发问题,以充分利用现代硬件的性能。并发编程正是解决这个问题的关键。
- 编程模型的发展:并发编程是现代编程模型的一个重要组成部分。随着云计算、大数据和物联网等技术的发展,我们需要处理的数据量和计算任务越来越大,因此需要使用并发编程来满足这些需求。
综上所述,C++并发编程是任何C++程序员都应该掌握的重要技能。在本文中,我们将深入探讨C++的并发编程工具,包括std::future、std::async、std::packaged_task和std::promise,并通过实例来展示如何使用它们来编写高效的并发程序。
1.3 关于std::future、std::async、std::packaged_task和std::promise的简介 (Introduction to std::future, std::async, std::packaged_task, and std::promise)
在C++的并发编程中,std::future、std::async、std::packaged_task和std::promise是四个非常重要的工具。它们都是C++ 11并发编程库的一部分,并在C++ 14、17、20等更高版本中得到了进一步的优化和改进。下面,我们对这四个工具进行简单的介绍:
- std::future:这是一个模板类,它代表一个异步操作的结果。这个异步操作可以是一个在另一个线程中运行的函数,或者是一个计算任务。std::future提供了一种机制,可以在结果准备好之后获取它,而不需要阻塞当前线程。
- std::async:这是一个函数,它用于启动一个异步任务,并返回一个std::future对象,该对象将在将来保存这个任务的结果。std::async提供了一种简单的方式来运行异步任务,并获取其结果。
- std::packaged_task:这是一个模板类,它封装了一个可调用对象(如函数或lambda表达式),并允许异步获取该对象的调用结果。当std::packaged_task对象被调用时,它会在内部执行封装的可调用对象,并将结果保存在一个std::future对象中。
- std::promise:这是一个模板类,它提供了一种手动设置std::future对象的结果的方式。当你有一个异步任务,并且这个任务的结果需要在多个地方使用时,std::promise可以非常有用。
这四个工具提供了一种强大的并发编程模型,它允许我们将计算任务分配到多个线程中,然后在需要的时候获取这些任务的结果。在后续的章节中,我们将详细介绍这四个工具的工作原理和使用方法,并通过示例代码来展示如何在实际的程序中使用它们。
二、std::future:异步结果的储存与获取
2.1 std::future的基本原理和结构
在并发编程中,我们常常需要在多个线程之间传递数据。std::future
是C++标准库中用于表示异步操作结果的类,它提供了一种非阻塞(或者说是异步)的方式来获取其他线程的计算结果。
基本原理
std::future
的工作原理很简单:你创建一个std::future
对象,然后把它传递给另一个线程,那个线程在某个时间点通过std::promise
或std::packaged_task
来设置结果。然后,你可以在任何时间点调用std::future::get()
来获取结果。如果结果还没准备好,get()
会阻塞当前线程直到结果可用。
std::future
是一个模板类,它的模板参数是它所表示的异步操作的结果类型。例如,std::future<int>
表示一个异步操作,其结果是一个整数。
结构与方法
std::future
主要包括以下几个方法:
get()
: 获取异步操作的结果。这个操作会阻塞,直到结果准备好。这个方法只能调用一次,因为它会销毁内部的结果状态。valid()
: 检查是否有一个与此std::future
关联的共享状态。如果有,返回true;否则,返回false。wait()
: 阻塞当前线程,直到异步操作完成。wait_for()
,wait_until()
: 这两个函数可以用来设置超时,如果在指定的时间内结果还没准备好,它们就会返回。
下面是std::future
的基本结构:
方法 | 描述 |
---|---|
get() | 获取异步操作的结果,如果结果还未准备好,会阻塞直到结果可用。 |
valid() | 检查是否有一个与此std::future 关联的共享状态。 |
wait() | 阻塞当前线程,直到异步操作完成。 |
wait_for() | 阻塞当前线程,直到异步操作完成或超过指定的等待时间。 |
wait_until() | 阻塞当前线程,直到异步操作完成或到达指定的时间点。 |
理解了std::future
的基本原理和结构,我们就可以开始探索它的实际应用了。在下一节中,我们将详细介绍std::future
的使用场景和示例代码。
2.2 std::future的使用场景和示例代码
std::future 的主要使用场景是获取异步操作的结果。它通常与 std::async、std::packaged_task 或 std::promise 配合使用,以便在异步任务完成时获取结果。
使用 std::async 启动异步任务
在这种情况下,std::async 用于启动异步任务,并返回一个 std::future 对象,该对象可以用于获取异步任务的结果。以下是一个例子:
#include <future>
#include <iostream>int compute() {// 假设这里有一些复杂的计算return 42;
}int main() {std::future<int> fut = std::async(std::launch::async, compute);// 在这里我们可以做其他的事情int result = fut.get(); // 获取异步任务的结果std::cout << "The answer is " << result << std::endl;return 0;
}
使用 std::packaged_task 包装可调用对象
std::packaged_task 可以包装一个可调用对象,并允许你获取该对象的调用结果。以下是一个例子:
#include <future>
#include <iostream>int compute() {// 假设这里有一些复杂的计算return 42;
}int main() {std::packaged_task<int()> task(compute);std::future<int> fut = task.get_future();// 在另一个线程中执行任务std::thread(std::move(task)).detach();int result = fut.get(); // 获取异步任务的结果std::cout << "The answer is " << result << std::endl;return 0;
}
使用 std::promise 显式设置异步操作的结果
std::promise 提供了一个手动设置异步操作结果的方式。这在你需要更多控制或在异步操作不能直接返回结果的情况下非常有用。
#include <future>
#include <iostream>
#include <thread>void compute(std::promise<int> prom) {// 假设这里有一些复杂的计算prom.set_value(42);
}int main() {std::promise<int> prom;std::future<int> fut = prom.get_future();// 在另一个线程中执行任务std::thread(compute, std::move(prom)).detach();int result = fut.get(); // 获取异步任务的结果std::cout << "The answer is " << result << std::endl;return 0;
}
以上就是 std::future 的主要使用场景和一些基本的示例代码。在下一节中,我们将探讨 std::future 在更高级的应用中的用法。
2.3 std::future在高级应用中的应用
std::future不仅可以用于简单的异步任务结果获取,更重要的是,它为编写复杂的并发和并行代码提供了基础。以下我们将介绍几个std::future在高级应用中的用法。
异步操作链
我们可以通过使用std::future和std::async创建异步操作链。在这个链中,一个操作的输出被用作下一个操作的输入,但这些操作可以在不同的线程上并发执行。
#include <future>
#include <iostream>int multiply(int x) {return x * 2;
}int add(int x, int y) {return x + y;
}int main() {std::future<int> fut = std::async(std::launch::async, multiply, 21);// 启动另一个异步任务,该任务需要等待第一个异步任务的结果std::future<int> result = std::async(std::launch::async, add, fut.get(), 20);std::cout << "The answer is " << result.get() << std::endl;return 0;
}
异步数据流管道
我们还可以使用std::future创建异步数据流管道,其中每个阶段可以在不同的线程上并发执行。
#include <future>
#include <iostream>
#include <queue>std::queue<std::future<int>> pipeline;void stage1() {for (int i = 0; i < 10; ++i) {auto fut = std::async(std::launch::async, [](int x) { return x * 2; }, i);pipeline.push(std::move(fut));}
}void stage2() {while (!pipeline.empty()) {auto fut = std::move(pipeline.front());pipeline.pop();int result = fut.get();std::cout << result << std::endl;}
}int main() {std::thread producer(stage1);std::thread consumer(stage2);producer.join();consumer.join();return 0;
}
这些只是std::future在高级应用中的一些示例。实际上,std::future是C++并发编程中非常重要的一部分,它可以被用于构建各种复杂的并发和并行结构。
异步任务之间的依赖关系
当我们有一些任务需要按照特定的顺序执行时,我们可以使用std::future来实现这种依赖关系。
#include <future>
#include <iostream>int task1() {// 假设这是一个耗时的任务return 42;
}int task2(int x) {// 这个任务依赖于task1的结果return x * 2;
}int main() {std::future<int> fut1 = std::async(std::launch::async, task1);std::future<int> fut2 = std::async(std::launch::async, task2, fut1.get());std::cout << "The answer is " << fut2.get() << std::endl;return 0;
}
在超时后取消异步任务
我们可以使用std::future::wait_for来实现在超时后取消异步任务的功能。
#include <future>
#include <iostream>
#include <chrono>void task() {// 假设这是一个可能会超时的任务std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "Task completed" << std::endl;
}int main() {std::future<void> fut = std::async(std::launch::async, task);std::future_status status = fut.wait_for(std::chrono::seconds(2));if (status == std::future_status::timeout) {std::cout << "Task cancelled due to timeout" << std::endl;} else {std::cout << "Task completed within timeout" << std::endl;}return 0;
}
注意,这个示例并不真正取消异步任务,而只是在任务超时后停止等待。真正的任务取消在C++中是一个更复杂的问题,需要使用其他的技术来实现。
这些示例只是展示了std::future的一部分用法,实际上std::future可以用于处理各种复杂的并发和并行问题。
三、std::async:异步任务的启动与管理 (std::async: Launching and Managing Asynchronous Tasks)
3.1 std::async的基本原理和结构 (Basic Principles and Structure of std::async)
C++11引入了std::async
,这是一种非常方便的异步执行机制,可以让我们更容易地实现并发和并行操作。std::async
可以立即启动一个异步任务,或者延迟执行,这取决于传递给它的启动策略。这个异步任务可以是函数、函数指针、函数对象、lambda表达式或者成员函数。
在C++中,std::async
函数返回一个std::future
对象,该对象可以用于获取异步任务的结果。在异步任务完成后,结果可以通过std::future::get()
函数获取。如果结果还未准备好,这个调用会阻塞,直到结果准备就绪。
下面是std::async
的基本原型:
template< class Function, class... Args >
std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>>async( Function&& f, Args&&... args );
这里的Function
参数是异步任务,Args
是传递给该任务的参数。std::async
返回一个std::future
,其模板参数是Function
的返回类型。
std::async
有两种模式:
- 异步模式 (
std::launch::async
):新的线程被立即启动并执行任务。 - 延迟模式 (
std::launch::deferred
):任务在future::get()
或future::wait()
被调用时执行。
默认模式是std::launch::async | std::launch::deferred
,即由系统决定是立即启动新线程,还是延迟执行。
std::async
的主要优势在于,它允许你将对结果的需求(通过std::future
)与任务的执行解耦,使得你能够更灵活地组织代码,而不必担心线程管理的细节。
3.2 std::async的使用场景和示例代码 (Use Cases and Example Code for std::async)
std::async
是异步编程的有力工具,它可以处理各种场景,如:并行计算、后台任务、延迟计算等。
下面,我们将通过一些示例代码来演示std::async
的使用。
3.2.1 基本的异步任务
这是一个简单的异步任务示例,我们在异步任务中计算斐波那契数列的一个元素:
#include <future>
#include <iostream>int fibonacci(int n) {if (n < 3) return 1;return fibonacci(n-1) + fibonacci(n-2);
}int main() {std::future<int> fut = std::async(fibonacci, 10);// 执行其他任务...int res = fut.get(); // 获取异步任务的结果std::cout << "The 10th Fibonacci number is " << res << "\n";return 0;
}
在上述代码中,我们创建了一个异步任务来计算斐波那契数列的第10个元素,然后继续执行其他任务。当我们需要斐波那契数的结果时,我们调用fut.get()
,如果此时异步任务已经完成,我们就可以立即得到结果;如果异步任务还未完成,那么调用会阻塞,直到结果可用。
3.2.2 异步执行模式和延迟执行模式
std::async
可以接受一个额外的参数,用于指定启动策略。这里是一个示例:
#include <future>
#include <iostream>
#include <thread>void do_something() {std::cout << "Doing something...\n";
}int main() {// 异步模式std::future<void> fut1 = std::async(std::launch::async, do_something);std::this_thread::sleep_for(std::chrono::seconds(1)); // 让主线程睡眠1秒fut1.get();// 延迟模式std::future<void> fut2 = std::async(std::launch::deferred, do_something);std::this_thread::sleep_for(std::chrono::seconds(1)); // 让主线程睡眠1秒fut2.get();return 0;
}
在上述代码中,我们首先以异步模式启动了一个任务,这个任务会立即在一个新线程中开始执行。然后,我们以延迟模式启动了另一个任务,这个任务会等到我们调用fut2.get()
时才开始执行。
3.2.3 错误处理
如果异步任务中抛出了异常,那么这个异常会被传播到调用std::future::get()
的地方。这使得错误处理变得简单:
#include <future>
#include <iostream>int calculate() {throw std::runtime_error("Calculation error!");return 0; // 这行代码永远不会被执行
}int main() {std::future<int> fut = std::async(calculate);try {int res = fut.get();} catch (const std::exception& e) {std::cout << "Caught exception: " << e.what() << "\n";}return 0;
}
在上述代码中,异步任务calculate()
抛出了一个std::runtime_error
异常。这个异常被传播到了主线程,我们在主线程中捕获了这个异常,并打印了异常的消息。
std::async
提供了一种简单、安全的方式来处理并发和并行编程,它将线程管理和结果获取的细节隐藏起来,让我们可以专注于实际的任务。
3.3 std::async在高级应用中的应用 (Applications of std::async in Advanced Use Cases)
std::async
不仅仅能用于简单的异步任务,还可以在一些高级的应用场景中发挥作用。这些应用通常涉及到大量的计算或者需要并行处理的场景。
3.3.1 并行算法
在需要处理大量数据的情况下,我们可以使用std::async
来并行化算法。例如,假设我们需要对一个数组进行排序,我们可以将数组分成两半,然后在两个异步任务中分别排序这两半,最后再合并结果。
这是一个并行排序的示例:
#include <algorithm>
#include <future>
#include <vector>template <typename T>
void parallel_sort(std::vector<T>& v) {if (v.size() <= 10000) { // 对于小数组,直接排序std::sort(v.begin(), v.end());} else { // 对于大数组,分成两半并行排序std::vector<T> v1(v.begin(), v.begin() + v.size() / 2);std::vector<T> v2(v.begin() + v.size() / 2, v.end());std::future<void> fut = std::async([&v1] { parallel_sort(v1); });parallel_sort(v2);fut.get();std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());}
}
在这个示例中,我们定义了一个并行排序函数parallel_sort
。如果数组的大小小于10000,我们直接对数组进行排序;如果数组的大小大于10000,我们将数组分成两半,然后在一个异步任务中排序第一半,在主线程中排序第二半,最后合并结果。
3.3.2 后台任务
在一些情况下,我们可能需要在后台执行一些任务,这些任务可能需要很长时间才能完成。例如,我们可能需要在后台下载一个文件,或者执行一些复杂的计算。std::async
提供了一种简单的方式来处理这种情况。
下面是一个在后台下载文件的示例:
#include <future>
#include <iostream>
#include <string>std::string download_file(const std::string& url) {// 用你的下载库下载文件...return "file content";
}int main() {std::future<std::string> fut = std::async(download_file, "http://example.com/file");// 在此处执行其他任务...std::string file_content = fut.get();std::cout << "Downloaded file content: " << file_content << "\n";return 0;
}
在这个示例中,我们在一个异步任务中下载一个文件,然后继续执行其他任务。当我们需要文件的内容时,我们调用fut.get()
获取结果。
3.3.3 异步日志系统
在许多系统中,日志系统是一个关键的组件,用于记录程序的运行情况。然而,写入日志可能是一个时间开销很大的操作,特别是当我们需要写入大量日志时。使用std::async
,我们可以将日志写入操作放在一个单独的线程中,从而避免阻塞主线程。
下面是一个异步日志系统的简单示例:
#include <future>
#include <iostream>
#include <string>void write_log(const std::string& log) {// 在这里写入日志...
}void log_async(const std::string& log) {std::async(std::launch::async, write_log, log);
}int main() {log_async("Start program");// 执行其他任务...log_async("End program");return 0;
}
在这个示例中,我们在一个异步任务中写入日志,然后立即返回,不等待日志写入完成。这样,我们就可以在不阻塞主线程的情况下写入日志。
3.3.4 实时计算系统
在一些实时计算系统中,我们可能需要在一定的时间内完成一些任务,否则就需要中止这些任务。std::async
和std::future
提供了一种简单的方式来实现这种需求。
下面是一个实时计算系统的示例:
#include <future>
#include <iostream>
#include <chrono>int calculate() {// 在这里执行一些复杂的计算...return 42;
}int main() {std::future<int> fut = std::async(std::launch::async, calculate);std::chrono::milliseconds span(100); // 最多等待100毫秒if (fut.wait_for(span) == std::future_status::ready) {int result = fut.get();std::cout << "Result is " << result << "\n";} else {std::cout << "Calculation did not finish in time\n";}return 0;
}
在这个示例中,我们在一个异步任务中执行计算,然后等待最多100毫秒。如果计算在这个时间内完成,我们就获取结果;否则,我们就打印一条消息,表示计算没有在时间内完成。
四、std::packaged_task:封装可调用目标的功能
4.1 std::packaged_task的基本原理和结构
std::packaged_task
是C++11引入的一种工具,它的主要作用是封装可调用的对象,如函数、lambda表达式、函数指针或函数对象,这使得我们可以在不同的上下文或线程中执行这些任务。std::packaged_task
对异步操作进行抽象,可以将其视为一个“包裹”,其中包含了异步操作的所有必要信息。
基本原理
在std::packaged_task
的内部,其将所封装的可调用对象和一个std::future
对象关联在一起。当我们调用std::packaged_task
对象时,它会执行所封装的任务,然后将结果存储在std::future
中。这样,我们就可以通过这个std::future
来获取任务的结果,无论任务是在哪个线程中完成的。
// 创建一个 packaged_task,它将 std::plus<int>() 封装起来
std::packaged_task<int(int, int)> task(std::plus<int>());// 获取与 task 关联的 future
std::future<int> result_future = task.get_future();// 在另一个线程中执行 task
std::thread(std::move(task), 5, 10).detach();// 在原线程中,我们可以从 future 中获取结果
int result = result_future.get(); // result == 15
结构
std::packaged_task
是一个模板类,其模板参数是可调用对象的类型。例如,如果我们有一个返回void
并接受一个int
参数的函数,那么我们可以创建一个std::packaged_task<void(int)>
的对象。
std::packaged_task
主要包含以下几个公有成员函数:
- 构造函数:用于构造
std::packaged_task
对象,并将可调用对象封装在内部。 operator()
: 用于调用封装的任务。valid()
: 用于检查std::packaged_task
是否含有一个封装的任务。get_future()
: 用于获取与std::packaged_task
关联的std::future
对象。swap()
: 用于交换两个std::packaged_task
对象的内容。
通过合理地使用std::packaged_task
,我们可以更好地管理异步任务,并从任何地方获取任务的结果。在C++并发编程中,这是一种非常有用的工具。
4.2 std::packaged_task的使用场景和示例代码
std::packaged_task
在多线程编程中有广泛的应用,主要适用于那些需要异步执行任务并获取结果的场景。下面是几个使用std::packaged_task
的典型场景:
- 异步任务执行:当你需要在另一个线程中执行任务,并且希望在当前线程中获取结果时,你可以使用
std::packaged_task
。 - 任务队列:你可以创建一个
std::packaged_task
的队列,将任务放入队列中,并由一个或多个工作线程来执行这些任务。 - Future/Promise模型:你可以使用
std::packaged_task
实现Future/Promise模型,其中std::future
用于获取结果,std::packaged_task
用于执行任务并存储结果。
以下是一个std::packaged_task
的使用示例:
#include <iostream>
#include <future>
#include <thread>// 一个要在子线程中执行的函数
int calculate(int x, int y) {return x + y;
}int main() {// 创建一个packaged_task,将calculate函数封装起来std::packaged_task<int(int, int)> task(calculate);// 获取与task关联的futurestd::future<int> result = task.get_future();// 创建一个新线程并执行taskstd::thread task_thread(std::move(task), 5, 10);// 在主线程中,我们可以从future中获取结果int result_value = result.get();std::cout << "Result: " << result_value << std::endl; // 输出: Result: 15task_thread.join();return 0;
}
在这个例子中,我们创建了一个std::packaged_task
对象task
,它将calculate
函数封装起来。然后我们在一个新的线程中执行task
,并在主线程中通过std::future
获取结果。这样我们就能够异步地执行任务,并在需要的时候获取结果。
4.3 std::packaged_task在高级应用中的应用
std::packaged_task
在复杂的多线程环境中有很多高级应用,比如任务队列、线程池和异步任务链等。以下将简要介绍几个应用案例。
任务队列
任务队列是一种常见的多线程设计模式,允许多个生产者线程提交任务,然后由一个或多个消费者线程执行这些任务。std::packaged_task
非常适合用来实现任务队列,因为它可以将任意的可调用对象封装成一个统一的接口。
#include <queue>
#include <future>
#include <mutex>// 任务队列
std::queue<std::packaged_task<int()>> tasks;
std::mutex tasks_mutex;// 生产者线程
void producer() {// 创建一个packaged_taskstd::packaged_task<int()> task([]() { return 7 * 7; });// 将task添加到任务队列中std::lock_guard<std::mutex> lock(tasks_mutex);tasks.push(std::move(task));
}// 消费者线程
void consumer() {// 从任务队列中取出一个task并执行std::lock_guard<std::mutex> lock(tasks_mutex);if (!tasks.empty()) {std::packaged_task<int()> task = std::move(tasks.front());tasks.pop();task();}
}
线程池
线程池是一种常见的多线程设计模式,它创建一定数量的线程,并复用这些线程来执行任务。std::packaged_task
可以用来实现线程池中的任务,因为它可以在一个线程中执行任务,并在另一个线程中获取结果。
异步任务链
异步任务链是一种设计模式,其中一个任务的结果被用作下一个任务的输入。std::packaged_task
可以用来实现异步任务链,因为它可以在任务完成时将结果存储在std::future
中,然后这个std::future
可以被下一个任务用来获取结果。
// 第一个任务
std::packaged_task<int()> task1([]() { return 7 * 7; });
std::future<int> future1 = task1.get_future();// 第二个任务,它的输入是第一个任务的结果
std::packaged_task<int(int)> task2([](int x) { return x + 1; });
std::future<int> future2 = task2.get_future();// 在一个线程中执行第一个任务
std::thread(std::move(task1)).detach();// 在另一个线程中执行第二个任务
std::thread([&]() {task2(future1.get());
}).detach();// 获取第二个任务的结果
int result = future2.get();
在这个例子中,我们创建了一个异步任务链,其中第一个任务计算7 * 7
,然后第二个任务将结果加一。我们在两个不同的线程中执行这两个任务,然
后在主线程中获取最终的结果。这展示了std::packaged_task
在高级并发编程中的强大能力。
特性\模型 | 任务队列 | 线程池 | 异步任务链 |
---|---|---|---|
适用场景 | 需要在多个线程中分发和执行任务,适合生产者-消费者模型 | 需要优化任务执行性能,避免频繁创建和销毁线程,适合并发高、任务量大的场景 | 任务之间存在依赖关系,下游任务需要使用上游任务的结果,适合数据处理和计算密集型任务 |
资源使用 | 可根据任务队列的长度动态调整线程数量,资源使用灵活 | 固定数量的线程,提前创建并复用,资源使用稳定 | 每个任务可能在不同的线程中执行,资源使用灵活,但可能需要更多的线程间同步 |
任务管理 | 任务通过队列进行管理,可以按照先进先出或优先级等策略调度任务 | 任务通常由线程池内部的任务队列进行管理,线程池负责任务的调度和执行 | 任务的管理需要根据任务之间的依赖关系进行,通常需要更复杂的逻辑 |
结果获取 | 通过std::future 获取结果,可以异步获取,或阻塞等待结果 | 通过std::future 获取结果,可以异步获取,或阻塞等待结果 | 通过std::future 获取结果,可以异步获取,或阻塞等待结果,下游任务可以直接使用上游任务的结果 |
错误处理 | 错误通常需要在执行任务的线程中捕获,并通过std::future 传递给获取结果的线程 | 错误通常需要在执行任务的线程中捕获,并通过std::future 传递给获取结果的线程 | 错误通常需要在执行任务的线程中捕获,并通过std::future 传递给获取结果的线程,错误可能会导致整个任务链的中断 |
五、std::promise:异步操作的结果承诺 (std::promise: Promise of Asynchronous Operation Results)
5.1 std::promise的基本原理和结构 (Basic Principles and Structure of std::promise)
std::promise 是一个在 C++11 及其后续版本中被引入的并发编程工具,它允许我们在一个线程中设置一个值或异常,然后在另一个线程中获取这个值或异常。这样的特性使得 std::promise 成为了一种强大的线程间通信手段。
设想一下,你正在组织一场盛大的晚会,而你需要为你的客人承诺他们会得到美味的食物。在这种情况下,你可能会雇佣一位厨师来准备食物。你向客人承诺(promise)将会有美食,而厨师则在背后工作,尽力满足你的承诺。在 C++ 中,这个过程就像一个线程(厨师)在工作,而另一个线程(你)在等待结果。
现在,让我们深入 std::promise 的底层原理和结构。
基本原理
std::promise 的基本原理很简单。当你创建一个 std::promise 对象时,你可以给它一个值或者一个异常。这个值或者异常可以被一个与该 promise 关联的 std::future 对象获取。这就是一个标准的“生产者-消费者”模型,在这个模型中,promise 是生产者,而 future 是消费者。
结构
std::promise 是一个模板类,它有一个模板参数 T,表示承诺的值的类型。一个 std::promise 对象可以通过它的成员函数 set_value 来设置一个值,或者通过成员函数 set_exception 来设置一个异常。这些值或异常可以通过与之关联的 std::future 对象来获取。
下面是一个 std::promise 的简单使用示例:
#include <iostream>
#include <future>
#include <thread>void my_promise(std::promise<int>& p) {std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟一些工作p.set_value(42); // 设置值
}int main() {std::promise<int> p;std::future<int> f = p.get_future(); // 获取 futurestd::thread t(my_promise, std::ref(p)); // 在新线程中运行函数std::cout << "Waiting for the answer...\n";std::cout << "The answer is " << f.get() << '\n'; // 获取值t.join();return 0;
}
这个示例中,my_promise 函数在一个新线程中运行,并设置 promise 的值为 42。主线程等待future 的值,然后打印出来。注意到,主线程会阻塞在 f.get() 处,直到 promise 的值被设置。
在下一节中,我们将详细介绍 std::promise 的使用场景和示例代码。
5.2 std::promise的使用场景和示例代码 (Use Cases and Example Code for std::promise)
使用场景
std::promise 最常见的使用场景是在多线程环境中进行线程间通信,尤其是当你需要在一个线程中设置一个值(或者一个异常),并在另一个线程中获取这个值(或异常)时。
此外,std::promise 可以用于以下场景:
- 异步任务:当你需要运行一个可能会花费很长时间的任务,并且你不想等待这个任务完成,你可以使用 std::promise 在一个新线程中运行这个任务,并在主线程中获取结果。
- 数据流管道:你可以使用一系列的 std::promise 和 std::future 对象来创建一个数据流管道,其中每个线程都是管道的一部分,并且每个线程都通过 std::promise 对象提供数据,然后通过 std::future 对象获取数据。
示例代码
让我们通过一个例子来展示如何使用 std::promise。在这个例子中,我们将使用一个 promise 来传递一个从新线程中计算出来的结果。
#include <iostream>
#include <future>
#include <thread>// 这个函数将会在一个新线程中被运行
void compute(std::promise<int>& p) {int result = 0;// 做一些计算...for (int i = 0; i < 1000000; ++i) {result += i;}// 计算完成,设置 promise 的值p.set_value(result);
}int main() {// 创建一个 promise 对象std::promise<int> p;// 获取与 promise 关联的 future 对象std::future<int> f = p.get_future();// 在新线程中运行 compute 函数std::thread t(compute, std::ref(p));// 在主线程中获取结果std::cout << "The result is " << f.get() << std::endl;// 等待新线程完成t.join();return 0;
}
在这个例子中,我们在新线程中运行了一个可能会花费很长时间的计算任务,并使用了一个 promise 来传递计算结果。在主线程中,我们通过 future 对象获取了这个结果。当我们调用 f.get()
时,主线程会阻塞,直到新线程完成计算并设置 promise 的值。
5.3 std::promise在高级应用中的应用 (Applications of std::promise in Advanced Use Cases)
std::promise 不仅仅可以在基础的多线程编程中使用,它也有一些高级应用场景,比如与其它并发工具结合使用以提高程序的性能和效率。以下是两个使用 std::promise 的高级应用场景:
高级应用一:链式异步任务
在某些情况下,你可能需要执行一系列的异步任务,其中每个任务的输入都依赖于前一个任务的输出。这种情况下,你可以创建一个 promise 和 future 的链,每个任务都有一个输入 future 和一个输出 promise,这样就可以确保任务的执行顺序,并且可以方便地获取每个任务的结果。
例如,下面的代码展示了如何使用 promise 和 future 的链来执行一系列的异步任务:
#include <iostream>
#include <future>
#include <thread>void chain_task(std::future<int>& f, std::promise<int>& p) {int input = f.get(); // 获取输入int output = input * 2; // 执行一些计算p.set_value(output); // 设置输出
}int main() {// 创建 promise 和 future 的链std::promise<int> p1;std::future<int> f1 = p1.get_future();std::promise<int> p2;std::future<int> f2 = p2.get_future();// 在新线程中运行异步任务std::thread t1(chain_task, std::ref(f1), std::ref(p2));std::thread t2(chain_task, std::ref(f2), std::ref(p1));// 设置初始输入p1.set_value(42);// 获取最终结果std::cout << "The final result is " << f1.get() << std::endl;// 等待新线程完成t1.join();t2.join();return 0;
}
高级应用二:与其它并发工具结合使用
std::promise 可以与 C++ 标准库中的其它并发工具结合使用,比如 std::async、std::packaged_task、std::thread 等,来创建更复杂的并发模式。
例如,你可以使用 std::async 来启动一个异步任务,并使用 std::promise 来传递任务的结果。你也可以使用 std::packaged_task 来封装一个可以在新线程中运行的任务,并使用 std::promise 来设置任务的结果。在这种情况下,std::promise 可以提供更高级的线程间通信机制,使你可以在不同的线程中共享数据和状态。
以上就是 std::promise 在高级应用中的一些使用场景,希望可以帮助你更好地理解并使用这个工具。在下一章中,我们将介绍 std::future、std::async、std::packaged_task 和 std::promise 的比较和选择。
六、并行类和线程池
并行库
std::future
是C++标准库的一部分,它表示将来可能在其他线程上计算出的一个值。std::future
本身并不直接涉及线程池。然而,它通常与如std::async
等机制结合使用,这些机制可以利用线程池执行异步任务。
事实上,std::async
的行为取决于给它的参数。如果传入参数 std::launch::async
,它将在新线程中执行任务。如果传入参数 std::launch::deferred
,任务将在调用 std::future::get()
时同步运行。无论如何,std::async
的实现可以使用线程池,这取决于标准库的实现和系统限制。
总之,std::future
并不直接与线程池有关,但它可以与使用线程池的异步执行机制一起使用。
C++标准库中,并没有直接提供线程池功能。std::future
和 std::async
只提供了一种基本的异步执行方式,所以在C++标准库中,你无法直接控制线程池的细节,例如工作线程数量、可调参数等。要实现这种控制,你可以创建自定义线程池,或使用已有的开源线程池库。
std::packaged_task
也可以与线程池一起使用,但它本身并不是一个线程池实现。std::packaged_task
是一个在C++中包装可调用对象的类模板,它允许将函数与一个 std::future
结合使用。当该可调用对象(函数、lambda表达式或函数对象)被调用时,std::packaged_task
会将结果存储起来,并使关联的 std::future
变得就绪。
你可以使用 std::packaged_task
创建任务,然后将这些任务提交给一个线程池。这使得在线程池中执行的任务能够返回一个 std::future
对象,从而对任务结果进行异步访问。
选择权衡
线程池通常更适用于长时间运行的任务,因为线程池意味着在执行时间较长的任务时可以复用线程资源。这样就能避免频繁地创建和销毁线程所带来的性能损失。线程池还允许你控制并发线程的数量来满足特定性能需求或系统限制。
而对于短时间且不频繁的任务,使用并行库(如C++标准库中的 std::async
、Intel TBB、Microsoft PPL和C++ Boost.Asio库)可能更恰当。这些库在只需执行少量任务时可以提供简便的接口,并避免为管理线程池带来额外的复杂性。并行库通常会处理线程创建和销毁的资源管理问题,因此对于这些罕见的任务是一个不错的选择。
请注意,在具体选择如何并发执行任务时,任务的性质(如任务是否有优先级、是否需要同步之类)以及所使用的库(它们会有不同的功能和优化)也是应该考虑的因素。
自定义线程池
以下是一个简单的自定义线程池示例:
#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>class ThreadPool {
public:ThreadPool(size_t num_threads);~ThreadPool();void enqueue(std::function<void()> task);private:void worker();std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex tasks_mutex;std::condition_variable tasks_cv;bool stop;
};ThreadPool::ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back(&ThreadPool::worker, this);}
}ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(tasks_mutex);stop = true;}tasks_cv.notify_all();for (auto &worker : workers) {worker.join();}
}void ThreadPool::enqueue(std::function<void()> task) {{std::unique_lock<std::mutex> lock(tasks_mutex);tasks.push(task);}tasks_cv.notify_one();
}void ThreadPool::worker() {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(tasks_mutex);tasks_cv.wait(lock, [this]() { return !tasks.empty() || stop; });if (stop && tasks.empty()) {return;}task = tasks.front();tasks.pop();}task();}
}
通过以上自定义线程池实现,你可以自由地控制线程池的大小,以及对任务队列进行管理。此外,还有许多开源线程池库可供选择,例如 Intel TBB,Microsoft PPL和C++ Boost.Asio库。这些库为多线程编程提供了更多的优化和高级控制。
并行库对线程池的帮助
C++中的并行类,包括std::thread、std::future、std::async、std::packaged_task和std::promise等,可以用来实现线程池,这对于提高多核处理器的利用率,减少线程创建和销毁的开销,以及提高程序的响应性能具有重要的帮助。下面我们详细讨论这些类如何辅助实现线程池。
1. std::thread
std::thread 是 C++ 的线程库中的基础,它可以用来创建和管理线程。在实现线程池时,我们通常会创建一组线程并保存在容器中(例如std::vector)。这些线程在创建时会开始执行一个特定的函数,这个函数通常是一个无限循环,不断从任务队列中取出任务并执行。
2. std::future和std::promise
std::future 和 std::promise 可以用来传递和获取任务的结果。在实现线程池时,我们通常会为每个任务创建一个 std::promise 对象,并将对应的 std::future 对象返回给调用者。当任务完成时,工作线程将结果设置到 std::promise 对象中,调用者可以通过 std::future 对象获取结果。
3. std::async
std::async 是一种简单的异步编程工具,它可以用来启动一个异步任务并返回一个 std::future 对象。虽然 std::async 本身并不适合用来实现线程池(因为它总是创建新的线程),但是我们可以借鉴它的设计来简化线程池的接口。具体来说,我们可以提供一个类似于 std::async 的函数,这个函数接受一个可调用对象和一组参数,将它们封装成任务并添加到任务队列中,然后返回一个 std::future 对象。
4. std::packaged_task
std::packaged_task 可以看作是一个包装了可调用对象的类,它将可调用对象和一个 std::promise 对象绑定在一起。当调用 std::packaged_task 对象时,它会调用内部的可调用对象,并将结果保存到 std::promise 对象中。在实现线程池时,我们可以用 std::packaged_task 来封装任务,这样就可以将任何可调用对象转换为一个可以放入任务队列的统一类型。
这些并行类提供了创建线程、异步执行任务和传递任务结果等基础功能,使得我们可以在 C++ 中实现高效的线程池。而线程池的使用可以更好地控制线程的数量,避免过多的线程创建和销毁带来的开销,提高多核处理器的利用率,从而提高程序的性能。
类名 | 功能描述 | 实现线程池的作用 | 用户编程的角度 | 实用性 |
---|---|---|---|---|
std::thread | 用于创建和管理线程 | 线程池的基础,负责执行任务 | 简单易用,但需要手动管理线程生命周期 | 高 |
std::future | 用于获取异步任务的结果 | 提供任务结果的获取方式,使调用者可以等待任务完成并获取结果 | 提供了一种安全且简单的方式来获取异步任务的结果 | 高 |
std::promise | 用于设置异步任务的结果 | 提供任务结果的设置方式,使工作线程可以设置任务的结果 | 需要与 std::future 配合使用,使用稍复杂 | 中 |
std::async | 用于启动异步任务 | 可以借鉴其设计来简化线程池的接口 | 非常简单易用,但不适合用于实现线程池 | 中 |
std::packaged_task | 用于封装任务 | 可以将任何可调用对象封装为任务,使任务可以被放入队列 | 简化了任务的创建和结果的传递,但需要手动管理其生命周期 | 高 |
并行库与线程池结合
以下是使用 std::thread
,std::future
,std::promise
,std::async
和 std::packaged_task
的自定义线程池实现。
#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>class ThreadPool {
public:// 构造函数: 创建指定数量的工作线程// Constructor: creates the specified number of worker threadsThreadPool(size_t num_threads);// 析构函数: 关闭所有线程并释放资源// Destructor: stops all threads and releases resources~ThreadPool();// 任务入队函数: 将任务添加到任务队列中// Enqueue function: adds a task to the task queuetemplate <typename F, typename... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;private:// 工作线程执行函数// Worker thread execution functionvoid worker();std::vector<std::thread> workers; // 工作线程std::queue<std::function<void()>> tasks; // 任务队列std::mutex tasks_mutex; // 保护任务队列的互斥锁std::condition_variable tasks_cv; // 通知工作线程的条件变量bool stop; // 标记线程池是否停止
};ThreadPool::ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back(&ThreadPool::worker, this);}
}ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(tasks_mutex);stop = true;}tasks_cv.notify_all();for (auto &worker : workers) {worker.join();}
}template <typename F, typename... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;// 创建 packaged_task,包装任务,将任务与 future 关联auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> result = task->get_future();{// 将任务包装为 std::function,并添加到任务队列std::unique_lock<std::mutex> lock(tasks_mutex);tasks.emplace([task](){ (*task)(); });}tasks_cv.notify_one(); // 通知一个工作线程return result;
}void ThreadPool::worker() {while (true) {std::function<void()> task;// 从任务队列中获取任务{std::unique_lock<std::mutex> lock(tasks_mutex);tasks_cv.wait(lock, [this]() { return !tasks.empty() || stop; });// 如果线程池已停止且没有剩余任务,则退出if (stop && tasks.empty()) {return;}task = tasks.front();tasks.pop();}// 执行任务task();}
}
此实现中,请注意以下关键部分:
- 构造函数初始化线程池并创建指定数量的工作线程。
enqueue()
函数是任务入队方法,可以将任务添加到任务队列中。它会创建一个std::packaged_task
并将任务与关联的std::future
对象关联起来。该方法返回一个std::future
对象,调用者可以使用它来获取异步任务的结果。- 线程池内的工作线程会等待并从任务队列中获取任务。执行完任务后,任务所对应的
std::future
对象将变为就绪状态,可以获取任务结果。 - 析构函数会停止所有工作线程并释放资源。
这个线程池提供了基本的线程管理功能,你可以根据需要进行扩展以支持其他功能,例如控制线程数量或提供任务优先级。
相关文章:
C++并发编程:std::future、std::async、std::packaged_task与std::promise的深度探索
C并发编程:std::future、std::async、std::packaged_task与std::promise的深度探索 一、引言 (Introduction)1.1 并发编程的概念 (Concept of Concurrent Programming)1.2 C并发编程的重要性 (Importance of Concurrent Programming in C)1.3 关于std::future、std:…...
测牛学堂:2023软件测试学习教程之sql的单表查询排序和模糊查询
单表查询的排序 关键字:order by 排序的类型,升序字段:ASC ,省略的话默认就是升序。 降序的字段:DESC 语法: order by 字段名 ASC| DESC返回的表则会按照给定的字段排序 例子:查询学生的考试成…...

CSS第一天总结
css第一天总结 css简介 CSS 是层叠样式表 ( Cascading Style Sheets ) 的简称. 有时我们也会称之为 CSS 样式表或级联样式表。 CSS 是也是一种标记语言 CSS 主要用于设置 HTML 页面中的文本内容(字体、大小、对齐方式等)、图片的外形(宽高、…...
js中各种console使用方法大全
console 1.console.log() (1)用于标准输出流的输出,也就是在控制台中显示一行信息。 (2)当传递多个参数时,控制台输出时将以空格分隔这些参数。 (3)也可以用占位符来定义输出的格…...
江西棒球未来发展规划·棒球1号位
关于江西棒球未来发展规划: 一、总体思路 江西棒球运动要立足当前,着眼长远,切实增强鼓励支持体育运动的社会氛围,弘扬体育精神,深化体育改革,加强体育基层建设,努力建设中国棒球之乡。把打造品牌赛事和培养明星运动员作为两手抓的发展方向,不断增强江西棒球运动的整体实力和…...
【笔记】做二休五
在记录去超市购物,菜场买菜,社区团购的花费时,将每个物品的价格记录下来。 大家应该善加利用自己所拥有的事物,若勉强想要利用自己没有的,只会让自己痛苦。 打扫&洗衣服 小苏打是可用于家庭清洁的万能清洁剂&…...

Qt6之字符串类内存分配新变化——16的次方增加
qt提供了比标准c string更强大,更丰富,更实用的字符串类QString,它的主要功能22个已经在之前逐一分析过,感兴趣的可前往以下链接查看,本文主要重点分析下qt在字符串类上面做的优化,主要是两个方面ÿ…...
C++ 名称空间
一、名称空间 1.1、引入名称空间的背景 在C中的名称可以是变量,函数,类以及类的成员。随着项目的增大,名称相互冲突的可能性也在增大。使用多个厂商的类库时,可能导致名称冲突。例如,两个库都定义了名为Listÿ…...
作为一名普通的java程序员,我想和大家分享一下4年来的工作内容
一直有小伙伴想了解更多关于我的工作内容,所以今天我来分享一下我作为一名普通java程序员,4年来工作内容发生了哪些变化,以及我有什么感悟。 我是16届毕业生,我的第一份工作是做外包,第一年的时间里测试偏多ÿ…...

CyberLink的专业视频编辑软件ActionDirector Ultra 3.0版本在win10系统的下载与安装配置教程
目录 前言一、ActionDirector Ultra安装二、使用配置总结 前言 ActionDirector Ultra是CyberLink公司开发的专业视频编辑软件,旨在帮助用户创作高质量的运动和冒险视频。该工具提供了一些先进的特效和编辑工具,让用户能够轻松地剪辑、修剪、调整颜色和添…...

在外远程访问公司局域网用友畅捷通T财务软件 - 远程办公
文章目录 前言1.本地访问简介2. cpolar内网穿透3. 公网远程访问4. 固定公网地址 前言 用友畅捷通T适用于异地多组织、多机构对企业财务汇总的管理需求;全面支持企业对远程仓库、异地办事处的管理需求;全面满足企业财务业务一体化管理需求。企业一般将其…...

VariantAutoencoder(VAE)中使用生成好的模型进行声音生成
文章目录 概述一、soundgenerator.py文件soundgenerator.py实现代码一、convert_spectrogram_to_audio方法librosa.db_to_amplitudelibrosa.istft generate方法 二、generate.py文件实现代码load_fsdd函数说明select_spectrogram函数说明save_signals函数说明main函数说明 三、…...

C++数据封装以及定义结构的详细讲解鸭~
名字:阿玥的小东东 博客主页:阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 定义结构 访问结构成员 结构作为函数参数 指向结构的指针 typedef 关键字 C 数据封装 数据封装的实例 设计策略 C 类 &…...

MySql 数据库的锁机制和原理
MySQL是一种流行的关系型数据库管理系统,广泛应用于各种Web应用程序和企业级应用程序中。在MySQL中,锁是一种用于控制并发访问的机制,它可以保证数据的一致性和完整性。本文将介绍MySQL的锁机制及原理,包括锁的类型、级别和实现原…...

try catch finally 里面有return的执行顺序
目录 实例结论 实例 1.try和catch中有return时,finally里面的语句会被执行吗 我们可以来分别看看 (1)执行try中的return时 public class Solution {public static int show() {try {return 1;}finally{System.out.println("finally模块被执行");}}publi…...

美团前高级测试工程师教你如何使用web自动化测试
一、自动化测试基本介绍 1 自动化测试概述: 什么是自动化测试?一般说来所有能替代人工测试的方式都属于自动化测试,即通过工具和脚本来模拟人执行用例的过程。 2 自动化测试的作用 减少软件测试时间与成本改进软件质量 通过扩大测试覆盖率…...

MySql.Data.dll 因版本问题造成报错的处理
NetCore 链接MySQL 报 Character set ‘utf8mb3‘ is not supported by .Net Framework 异常解决_character set utf8mb3_csdn_aspnet的博客-CSDN博客 查看mysql版本号,两种办法: 第一种在数据库中执行查询:SELECT version; 第二种使用工具…...
囚徒困境——从博弈论的角度解释“美女配丑男”
前言 有一种很常见的现象,美女配丑男。其实这种现象背后是有一定科学原理的。本文将从博弈论的角度,从囚徒困境出发解释这一现象产生的原因。 囚徒困境 囚徒困境的经典案例 先来介绍一下经典的囚徒困境。 警方逮捕甲、乙两名嫌疑犯,但没有…...
运算符重载函数作为类的成员函数——有理数的约分
目录 一、题目 二、代码 三、算法分析 (一)数学表达式 (二) 代码实现 一)运算符重载函数 二)优化函数(实现有理数约分) 一、题目 通过运算符重载为类的成员函数来实现两个有…...
mysql数据库的内置函数--7
目录 内置函数 日期函数 字符串函数 数学函数 其它函数 内置函数 在mysql中这些函数用select进行使用 日期函数 函数描述NOW()返回当前的日期和时间CURDATE()返回当前的日期CURTIME()返回当前的时间DATE()从日期或日期/时间表达式中提取日期部分TIME()从日期或日期/时间…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

生产管理系统开发:专业软件开发公司的实践与思考
生产管理系统开发的关键点 在当前制造业智能化升级的转型背景下,生产管理系统开发正逐步成为企业优化生产流程的重要技术手段。不同行业、不同规模的企业在推进生产管理数字化转型过程中,面临的挑战存在显著差异。本文结合具体实践案例,分析…...

docker容器互联
1.docker可以通过网路访问 2.docker允许映射容器内应用的服务端口到本地宿主主机 3.互联机制实现多个容器间通过容器名来快速访问 一 、端口映射实现容器访问 1.从外部访问容器应用 我们先把之前的删掉吧(如果不删的话,容器就提不起来,因…...
Redis——Cluster配置
目录 分片 一、分片的本质与核心价值 二、分片实现方案对比 三、分片算法详解 1. 范围分片(顺序分片) 2. 哈希分片 3. 虚拟槽分片(Redis Cluster 方案) 四、Redis Cluster 分片实践要点 五、经典问题解析 C…...