C++20 中的同步输出流:`std::basic_osyncstream` 深入解析与应用实践

文章目录

    • 一、`std::basic_osyncstream` 的背景与动机
    • 二、`std::basic_osyncstream` 的基本原理
    • 三、`std::basic_osyncstream` 的使用方法
      • (一)基本用法
      • (二)多线程环境下的使用
      • (三)与文件流的结合
    • 四、`std::basic_osyncstream` 的高级特性
      • (一)缓冲区管理
      • (二)与其他 C++20 特性的结合
        • 1\. 与 `std::format` 的结合
        • 2\. 与协程的结合
    • 五、`std::basic_osyncstream` 的性能分析
      • (一)同步机制的开销
      • (二)缓冲区管理的开销
      • (三)性能优化建议
    • 六、`std::basic_osyncstream` 的应用场景
      • (一)日志系统
      • (二)多线程数据处理
      • (三)文件写入
    • 七、`std::basic_osyncstream` 的实现原理
      • (一)`std::basic_syncbuf` 的角色
      • (二)线程安全的实现机制
      • (三)缓冲区刷新策略
    • 八、`std::basic_osyncstream` 的优势与局限性
      • (一)优势
      • (二)局限性
    • 九、`std::basic_osyncstream` 的最佳实践
      • (一)合理设置缓冲区大小
      • (二)减少不必要的同步操作
      • (三)使用线程池
      • (四)避免过度使用 `std::basic_osyncstream`
    • 十、`std::basic_osyncstream` 的未来展望
      • (一)性能优化
      • (二)功能扩展
      • (三)与其他特性的集成
    • 十一、总结

在多线程编程中,输出流的同步问题一直是困扰开发者的一大难题。传统的 std::ostream(如 std::cout)在多线程环境下无法保证输出的顺序性和完整性,容易导致输出内容交织、顺序混乱等问题。为了解决这一问题,C++20 引入了 std::basic_osyncstream,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。

一、std::basic_osyncstream 的背景与动机

在多线程程序中,多个线程可能同时尝试向同一个输出流(如控制台或文件)写入数据。由于 std::ostream 本身并不提供线程安全机制,这种并发写入会导致数据竞争(race condition),使得输出结果不可预测。例如,以下代码展示了在多线程环境下使用 std::cout 输出时可能出现的问题:

#include <iostream>
#include <thread>
#include <vector>void print_thread_id(int id) {std::cout << "Thread " << id << " is running\n";
}int main() {constexpr int num_threads = 5;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(print_thread_id, i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,多个线程同时向 std::cout 输出,可能会导致输出内容交错,例如:

Thread 0 is runningThread 1 is running
Thread 2 is running
Thread 3 is running
Thread 4 is running

为了解决这种问题,C++20 引入了 std::basic_osyncstream。它通过为每个线程提供独立的缓冲区,并在适当的时候将缓冲区的内容原子式地写入目标流,从而保证了输出的顺序性和完整性。

二、std::basic_osyncstream 的基本原理

std::basic_osyncstreamstd::basic_syncbuf 的便捷包装器。其核心思想是利用 RAII(Resource Acquisition Is Initialization)机制,为每个线程创建一个独立的同步缓冲区(sync buffer)。当线程向 std::basic_osyncstream 写入数据时,数据首先被写入到线程的独立缓冲区中,而不是直接写入目标流。只有在以下两种情况下,缓冲区的内容才会被原子式地写入目标流:

  1. 对象析构:当 std::basic_osyncstream 对象析构时,其内部的缓冲区内容会被自动写入目标流。
  2. 显式刷新:调用 std::basic_osyncstreamemit 方法或插入换行符(如 std::endl)时,缓冲区的内容会被刷新到目标流。

这种设计使得 std::basic_osyncstream 能够在不牺牲性能的前提下,提供线程安全的输出流操作。

三、std::basic_osyncstream 的使用方法

(一)基本用法

std::basic_osyncstream 是一个模板类,它依赖于底层流类型(如 std::ostreamstd::wostream)。要使用 std::basic_osyncstream,首先需要包含头文件 <syncstream>,然后创建一个 std::basic_osyncstream 对象,并将其绑定到一个底层流对象上。以下是一个简单的示例:

#include <iostream>
#include <syncstream>int main() {std::osyncstream sync_out(std::cout); // 创建同步输出流对象sync_out << "Hello, ";sync_out << "World!\n";return 0;
}

在上述代码中,std::osyncstream 对象 sync_out 将输出绑定到 std::cout。由于 std::osyncstream 的存在,即使在多线程环境下,输出内容也不会交错。

(二)多线程环境下的使用

std::basic_osyncstream 的主要优势在于它能够解决多线程环境下的输出同步问题。以下是一个多线程输出的示例:

#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>void print_thread_info(std::basic_osyncstream<std::ostream>& sync_out, int id) {sync_out << "Thread " << id << " is running\n";
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(print_thread_info, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,多个线程通过 std::basic_osyncstream 对象 sync_outstd::cout 输出信息。由于 std::basic_osyncstream 的同步机制,每个线程的输出都能够按顺序输出,而不会出现内容交错的情况。

(三)与文件流的结合

std::basic_osyncstream 不仅可以与 std::cout 结合使用,还可以与文件流(如 std::ofstream)一起使用。以下是一个将输出写入文件的示例:

#include <fstream>
#include <syncstream>
#include <thread>
#include <vector>void write_to_file(std::basic_osyncstream<std::ofstream>& sync_out, int id) {sync_out << "Thread " << id << " is writing to file\n";
}int main() {std::ofstream file("output.txt");std::basic_osyncstream<std::ofstream> sync_out(file);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(write_to_file, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,多个线程通过 std::basic_osyncstream 对象 sync_out 将数据写入文件 output.txt。由于 std::basic_osyncstream 的同步机制,文件中的输出内容是按顺序排列的。

四、std::basic_osyncstream 的高级特性

(一)缓冲区管理

std::basic_osyncstream 的底层依赖于 std::basic_syncbuf,它负责管理缓冲区。std::basic_syncbuf 提供了灵活的缓冲区管理机制,允许开发者自定义缓冲区的大小和行为。例如,可以通过以下方式设置缓冲区的大小:

#include <syncstream>
#include <iostream>int main() {std::osyncstream sync_out(std::cout);sync_out.rdbuf()->pubsetbuf(nullptr, 0); // 禁用缓冲区sync_out << "Hello, World!\n";return 0;
}

在上述代码中,通过调用 pubsetbuf 方法,可以禁用缓冲区或设置缓冲区的大小。

(二)与其他 C++20 特性的结合

C++20 引入了许多新特性,如 std::format 和协程(Coroutines)。std::basic_osyncstream 可以与这些特性结合使用,进一步提升代码的可读性和性能。

1. 与 std::format 的结合

std::format 提供了一种安全、灵活的字符串格式化机制。将 std::basic_osyncstreamstd::format 结合使用,可以简化多线程环境下的日志输出。以下是一个示例:

#include <iostream>
#include <format>
#include <syncstream>
#include <thread>void log_message(std::basic_osyncstream<std::ostream>& sync_out, int thread_id, int value) {sync_out << std::format("Thread [{}] reports value: {}\n", thread_id, value);
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::thread t1(log_message, std::ref(sync_out), 1, 42);std::thread t2(log_message, std::ref(sync_out), 2, 100);t1.join();t2.join();return 0;
}

在上述代码中,std::format 负责格式化字符串,而 std::basic_osyncstream 负责同步输出。这种组合使得日志输出既安全又高效。

2. 与协程的结合

协程是 C++20 中引入的一种新的并发编程机制。std::basic_osyncstream 可以与协程结合使用,实现更复杂的并发输出逻辑。以下是一个简单的示例:

#include <iostream>
#include <syncstream>
#include <coroutine>
#include <thread>struct AsyncLog {std::basic_osyncstream<std::ostream>& sync_out;int thread_id;struct promise_type {AsyncLog get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};void await_resume() {}void await_suspend(std::coroutine_handle<> h) {sync_out << "Thread " << thread_id << " is logging\n";h.resume();}
};void log_thread(std::basic_osyncstream<std::ostream>& sync_out, int id) {AsyncLog{sync_out, id}.await_suspend(std::noop_coroutine());
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::thread t1(log_thread, std::ref(sync_out), 1);std::thread t2(log_thread, std::ref(sync_out), 2);t1.join();t2.join();return 0;
}

在上述代码中,AsyncLog 是一个协程,它通过 std::basic_osyncstream 同步输出日志信息。这种结合使得协程能够与同步输出流无缝协作。

五、std::basic_osyncstream 的性能分析

虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但它可能会引入一定的性能开销。主要的性能开销来自于同步机制和缓冲区管理。以下是一些性能分析的关键点:

(一)同步机制的开销

std::basic_osyncstream 的同步机制基于互斥锁(mutex)。每次线程向 std::basic_osyncstream 写入数据时,都会尝试获取互斥锁。如果多个线程同时尝试写入,可能会导致线程阻塞,从而影响性能。然而,这种开销通常是可以接受的,因为它能够保证输出的顺序性和完整性。

(二)缓冲区管理的开销

std::basic_osyncstream 使用缓冲区来减少对底层流的写入操作。虽然缓冲区可以提高性能,但缓冲区的大小和刷新策略也会影响性能。如果缓冲区过大,可能会导致内存占用增加;如果缓冲区过小,可能会导致频繁的刷新操作。因此,合理设置缓冲区大小是优化性能的关键。

(三)性能优化建议

为了优化 std::basic_osyncstream 的性能,可以采取以下措施:

  1. 合理设置缓冲区大小:根据实际需求调整缓冲区大小,避免缓冲区过大或过小。
  2. 减少不必要的同步操作:如果输出内容较短,可以考虑使用 std::endlstd::flush 显式刷新缓冲区,而不是依赖析构函数自动刷新。
  3. 使用线程池:在多线程环境下,使用线程池可以减少线程创建和销毁的开销,从而提高性能。

六、std::basic_osyncstream 的应用场景

std::basic_osyncstream 在多线程编程中具有广泛的应用场景,以下是一些典型的例子:

(一)日志系统

在多线程应用程序中,日志系统是必不可少的。std::basic_osyncstream 可以用于实现线程安全的日志输出,确保日志信息的顺序性和完整性。以下是一个简单的日志系统实现:

#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>class Logger {
public:static void log(const std::string& message) {std::basic_osyncstream<std::ostream> sync_out(std::cout);sync_out << message << std::endl;}
};void worker_thread(int id) {Logger::log("Thread " + std::to_string(id) + " is running");
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(worker_thread, i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,Logger 类使用 std::basic_osyncstream 实现线程安全的日志输出。多个线程通过 Logger::log 方法输出日志信息,而不会出现内容交错的情况。

(二)多线程数据处理

在多线程数据处理中,std::basic_osyncstream 可以用于输出处理结果。以下是一个简单的数据处理示例:

#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>void process_data(std::basic_osyncstream<std::ostream>& sync_out, int data) {// 模拟数据处理int result = data * 2;sync_out << "Thread " << std::this_thread::get_id() << " processed data: " << data << ", result: " << result << std::endl;
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(process_data, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,多个线程通过 std::basic_osyncstream 输出数据处理结果。由于 std::basic_osyncstream 的同步机制,输出内容是按顺序排列的。

(三)文件写入

在多线程环境下,向文件写入数据时也需要保证线程安全。std::basic_osyncstream 可以与文件流结合使用,实现线程安全的文件写入。以下是一个示例:

#include <fstream>
#include <syncstream>
#include <thread>
#include <vector>void write_to_file(std::basic_osyncstream<std::ofstream>& sync_out, int data) {sync_out << "Thread " << std::this_thread::this_thread::get_id() << " wrote data: " << data << std::endl;
}int main() {std::ofstream file("output.txt");std::basic_osyncstream<std::ofstream> sync_out(file);std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(write_to_file, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}

在上述代码中,多个线程通过 std::basic_osyncstream 向文件 output.txt 写入数据。由于 std::basic_osyncstream 的同步机制,文件中的输出内容是按顺序排列的,不会出现数据交错的情况。

七、std::basic_osyncstream 的实现原理

为了更好地理解 std::basic_osyncstream 的工作原理,我们需要深入探讨其底层实现机制。std::basic_osyncstream 是基于 std::basic_syncbuf 的一个封装,而 std::basic_syncbuf 是 C++20 中引入的一个同步缓冲区类模板。

(一)std::basic_syncbuf 的角色

std::basic_syncbufstd::basic_osyncstream 的底层缓冲区管理器。它继承自 std::basic_streambuf,并重写了关键的虚函数,如 overflowsync,以实现同步写入。std::basic_syncbuf 的主要职责是:

  1. 缓冲区管理:为每个线程分配独立的缓冲区,减少对底层流的直接写入操作,从而提高性能。
  2. 同步写入:在缓冲区满或显式刷新时,将缓冲区的内容原子式地写入底层流,确保线程安全。

(二)线程安全的实现机制

std::basic_syncbuf 使用互斥锁(mutex)来实现线程安全的写入操作。当一个线程尝试写入数据时,它会首先获取互斥锁,然后将数据写入缓冲区。如果缓冲区满了或者调用了 emit 方法,缓冲区的内容会被刷新到底层流。在刷新过程中,互斥锁会确保只有一个线程能够访问底层流,从而避免数据竞争。

(三)缓冲区刷新策略

std::basic_syncbuf 的缓冲区刷新策略是影响性能的关键因素之一。缓冲区的刷新可以通过以下几种方式触发:

  1. 显式刷新:调用 std::basic_osyncstreamemit 方法或插入换行符(如 std::endl)时,缓冲区的内容会被刷新到底层流。
  2. 缓冲区满:当缓冲区达到其最大容量时,缓冲区的内容会被自动刷新到底层流。
  3. 对象析构:当 std::basic_osyncstream 对象析构时,其内部的缓冲区内容会被自动写入底层流。

八、std::basic_osyncstream 的优势与局限性

(一)优势

  1. 线程安全std::basic_osyncstream 提供了线程安全的输出流操作,解决了多线程环境下的输出混乱问题。
  2. 性能优化:通过缓冲区管理,减少了对底层流的直接写入操作,从而提高了性能。
  3. 易用性std::basic_osyncstream 的使用方法与传统的 std::ostream 类似,易于上手。
  4. 灵活性:可以与多种底层流(如 std::coutstd::ofstream)结合使用,满足不同的输出需求。

(二)局限性

  1. 性能开销:虽然 std::basic_osyncstream 通过缓冲区管理减少了对底层流的写入操作,但同步机制本身仍会引入一定的性能开销。特别是在高并发场景下,互斥锁的争用可能会导致线程阻塞,从而影响性能。
  2. 缓冲区管理的复杂性:合理设置缓冲区大小是优化性能的关键,但缓冲区大小的设置需要根据具体应用场景进行调整。如果缓冲区过大,可能会导致内存占用增加;如果缓冲区过小,可能会导致频繁的刷新操作。
  3. 对底层流的依赖std::basic_osyncstream 的性能和行为在很大程度上依赖于底层流的实现。例如,如果底层流的写入操作本身就很慢,std::basic_osyncstream 的性能也会受到影响。

九、std::basic_osyncstream 的最佳实践

为了充分发挥 std::basic_osyncstream 的优势,同时避免其局限性带来的影响,以下是一些最佳实践建议:

(一)合理设置缓冲区大小

缓冲区大小的设置需要根据具体应用场景进行调整。一般来说,缓冲区大小应该根据以下因素进行权衡:

  1. 内存占用:较大的缓冲区会占用更多的内存,但可以减少对底层流的写入操作,从而提高性能。
  2. 刷新频率:较小的缓冲区会导致更频繁的刷新操作,从而增加性能开销。
  3. 输出延迟:较大的缓冲区可能会导致输出延迟增加,因为数据需要在缓冲区中积累到一定程度才会被刷新。

在实际应用中,可以通过实验和性能测试来确定最优的缓冲区大小。例如,可以通过以下代码设置缓冲区大小:

#include <iostream>
#include <syncstream>int main() {std::osyncstream sync_out(std::cout);sync_out.rdbuf()->pubsetbuf(nullptr, 1024); // 设置缓冲区大小为 1024 字节sync_out << "Hello, World!\n";return 0;
}

(二)减少不必要的同步操作

虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但过多的同步操作可能会引入不必要的性能开销。为了减少同步操作,可以采取以下措施:

  1. 显式刷新缓冲区:如果输出内容较短,可以考虑使用 std::endlstd::flush 显式刷新缓冲区,而不是依赖析构函数自动刷新。显式刷新可以减少缓冲区的占用时间,从而降低同步操作的开销。
  2. 批量写入:尽量将多个输出操作合并为一个批量操作,减少对 std::basic_osyncstream 的调用次数。例如,可以通过以下代码实现批量写入:
#include <iostream>
#include <syncstream>
#include <sstream>int main() {std::osyncstream sync_out(std::cout);std::ostringstream oss;oss << "Hello, " << "World!\n";sync_out << oss.str();return 0;
}

在上述代码中,通过 std::ostringstream 将多个输出操作合并为一个字符串,然后一次性写入 std::basic_osyncstream,从而减少了同步操作的次数。

(三)使用线程池

在多线程环境下,线程的创建和销毁是一个相对耗时的操作。使用线程池可以减少线程的创建和销毁次数,从而提高性能。线程池预先创建了一组线程,并在需要时将任务分配给这些线程。这样可以避免频繁创建和销毁线程带来的开销。

以下是一个简单的线程池实现示例:

#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>class ThreadPool {
public:ThreadPool(size_t num_threads) {for (size_t i = 0; i < num_threads; ++i) {threads.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queue_mutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) {return;}task = std::move(tasks.front());tasks.pop();}task();}});}}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (auto& t : threads) {t.join();}}template <typename F, typename... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;}private:std::vector<std::thread> threads;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop = false;
};void print_message(std::basic_osyncstream<std::ostream>& sync_out, const std::string& message) {sync_out << message << std::endl;
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);ThreadPool pool(4);pool.enqueue(print_message, std::ref(sync_out), "Message from thread 1");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 2");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 3");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 4");return 0;
}

在上述代码中,ThreadPool 类管理了一个线程池,enqueue 方法用于将任务提交到线程池中。通过使用线程池,可以减少线程的创建和销毁次数,从而提高性能。

(四)避免过度使用 std::basic_osyncstream

虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但在某些情况下,过度使用可能会导致不必要的性能开销。例如,如果输出操作本身不需要线程安全,或者可以通过其他方式实现线程安全,那么可以考虑不使用 std::basic_osyncstream

以下是一些可以避免使用 std::basic_osyncstream 的情况:

  1. 单线程环境:如果程序运行在单线程环境中,那么可以使用传统的 std::ostream,而无需使用 std::basic_osyncstream
  2. 独立输出流:如果每个线程都有自己的独立输出流,那么可以避免使用 std::basic_osyncstream。例如,可以为每个线程创建一个独立的文件流,从而避免线程间的竞争。
  3. 日志系统:在某些情况下,可以使用专门的日志库(如 spdloglog4cpp)来实现线程安全的日志输出,而无需使用 std::basic_osyncstream。这些日志库通常提供了更高效的线程安全机制和更丰富的功能。

十、std::basic_osyncstream 的未来展望

std::basic_osyncstream 是 C++20 中引入的一个重要特性,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。随着 C++ 标准的不断发展,std::basic_osyncstream 也可能会得到进一步的改进和优化。

以下是一些可能的发展方向:

(一)性能优化

随着硬件技术的不断发展,多核处理器的性能越来越高。为了充分发挥多核处理器的性能,std::basic_osyncstream 可能会引入更多的性能优化措施。例如,可以使用无锁编程技术(lock-free programming)来减少互斥锁的开销,从而提高性能。

(二)功能扩展

std::basic_osyncstream 目前主要支持输出流的同步操作,但未来可能会扩展其功能,支持更多的同步操作类型。例如,可以引入同步输入流(std::basic_isyncstream),从而实现线程安全的输入操作。

(三)与其他特性的集成

C++ 标准中引入了许多新特性,如协程(Coroutines)、模块(Modules)和概念(Concepts)。未来,std::basic_osyncstream 可能会与这些特性进一步集成,从而提供更强大的功能。例如,可以将协程与 std::basic_osyncstream 结合使用,实现更复杂的并发输出逻辑。

十一、总结

std::basic_osyncstream 是 C++20 中引入的一个重要特性,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。通过使用 std::basic_osyncstream,可以避免多线程环境下的输出混乱问题,提高程序的可读性和可维护性。

在使用 std::basic_osyncstream 时,需要注意其性能开销和局限性。通过合理设置缓冲区大小、减少不必要的同步操作和使用线程池等措施,可以充分发挥 std::basic_osyncstream 的优势,同时避免其局限性带来的影响。

随着 C++ 标准的不断发展,std::basic_osyncstream 也可能会得到进一步的改进和优化。未来,我们可以期待 std::basic_osyncstream 在性能、功能和与其他特性的集成方面取得更大的进步。

总之,std::basic_osyncstream 是一个多线程编程中不可或缺的工具,它为开发者提供了一种简单而强大的方式来解决多线程环境下的输出流同步问题。通过深入理解其原理和使用方法,我们可以更好地利用这一特性,提升程序的质量和性能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C/C++蓝桥杯算法真题打卡(Day8)

一、P8780 [蓝桥杯 2022 省 B] 刷题统计 - 洛谷 算法代码&#xff1a; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;方便使用各种数据结构和算法 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用标准库函数时都要加 std::in…

JavaScript 编程:从基础到高级应用的全面探索

引言 JavaScript 作为一种广泛应用于 Web 开发的脚本语言&#xff0c;已经成为现代互联网不可或缺的一部分。它不仅可以为网页增添交互性和动态效果&#xff0c;还能在服务器端&#xff08;如 Node.js&#xff09;进行后端开发。本文将从 JavaScript 的基础语法开始&#xff0…

第十三次CCF-CSP认证(含C++源码)

第十三次CCF-CSP认证 跳一跳满分题解 碰撞的小球满分题解遇到的问题 棋局评估满分题解 跳一跳 题目链接 满分题解 没什么好说的 基本思路就是如何用代码翻译题目所给的一些限制&#xff0c;以及变量应该如何更新&#xff0c;没像往常一样给一个n&#xff0c;怎么读入数据&…

Pytorch使用手册—自定义函数的双重反向传播与自定义函数融合卷积和批归一化(专题五十二)

1. 使用自定义函数的双重反向传播 有时候,在反向计算图中运行两次反向传播是有用的,例如计算高阶梯度。然而,支持双重反向传播需要对自动求导(autograd)有一定的理解,并且需要小心处理。支持单次反向传播的函数不一定能够支持双重反向传播。在本教程中,我们将展示如何编…

MySQL:数据库基础

数据库基础 1.什么是数据库&#xff1f;2.为什么要学习数据库&#xff1f;3.主流的数据库&#xff08;了解&#xff09;4.服务器&#xff0c;数据库&#xff0c;表之间的关系5.数据的逻辑存储6.MYSQL架构7.存储引擎 1.什么是数据库&#xff1f; 数据库(Database,简称DB)&#x…

Web Component 教程(五):从 Lit-html 到 LitElement,简化组件开发

前言 在现代前端开发中&#xff0c;Web 组件是一种非常流行的技术&#xff0c;它允许我们创建可重用的、自包含的 UI 元素。而 Lit-html 是一个简洁高效库&#xff0c;用于在 Web 组件中进行渲染。在这篇教程中&#xff0c;我们一步步学习如何 Lit-html 来创建 Web Component。…

【C++】二叉树和堆的链式结构(上)

本篇博客给大家带来的是用C语言来实现堆链式结构和二叉树的实现&#xff01; &#x1f41f;&#x1f41f;文章专栏&#xff1a;数据结构 &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享&#xff01; 今日思想&#xff…

Devops之AWS:如何安装AWS CLI

AWS 命令行界面&#xff08;AWS CLI&#xff09;是一种开源工具&#xff0c;让我们能够使用命令行 Shell 中的命令与 AWS 服务进行交互。 安装步骤&#xff1a; 下载并运行AWS CLI的MSI安装程序&#xff1a; 点击如下的链接&#xff0c;即可下载MSI安装程序&#xff1a; htt…

PH2D数据集: 用人类演示数据提升人形机器人操作能力,助力跨实体学习

2025-03-18, 由加州大学圣地亚哥分校, 卡内基梅隆大学, 华盛顿大学, 麻省理工学院等机构联合收集了PH2D数据集。该数据集包含26824个任务导向的人类演示&#xff0c;采用消费者级VR设备收集&#xff0c;提供了准确的3D手部关键点姿态和语言注释。数据集覆盖了多种操作任务、不同…

python 数据可视化matplotib库安装与使用

要使用 matplotlib 库进行数据可视化&#xff0c;首先你需要确保已经安装了该库。如果你还没有安装&#xff0c;可以通过 Python 的包管理器 pip 来安装它。在你的命令行工具中运行以下命令来安装 matplotlib&#xff1a; pip install matplotlib安装完成后&#xff0c;你就可以…

【MySQL基础-10】MySQL中的LENGTH()函数:用法详解与实例分析

在MySQL数据库中&#xff0c;LENGTH()函数是一个非常常用的字符串函数&#xff0c;用于计算字符串的字节长度。理解并掌握LENGTH()函数的用法&#xff0c;对于处理字符串数据、优化查询以及进行数据验证都非常有帮助。本文将详细介绍LENGTH()函数的用法&#xff0c;并通过实例演…

Matlab 基于专家pid控制的时滞系统

1、内容简介 Matlab 185-基于专家pid控制的时滞系统 可以交流、咨询、答疑 2、内容说明 略 在处理时滞系统&#xff08;Time Delay Systems&#xff09;时&#xff0c;使用传统的PID控制可能会面临挑战&#xff0c;因为时滞会导致系统的不稳定或性能下降。专家PID控制通过结…

E902基于bash与VCS的仿真环境建立

网上看见很多E902仿真的文章&#xff0c;但用到的编译器是类似于这种Xuantie-900-gcc-elf-newlib-x86_64-V3.0.1-20241120&#xff0c;而我按照相应的步骤与对应的编译器&#xff0c;仿真总会报错。后面将编译器换成riscv64-elf-x86_64-20210512&#xff0c;反而成功了。现在开…

SpringSecurity配置(自定义认证过滤器)

文末有本篇文章的项目源码文件可供下载学习 在这个案例中,我们已经实现了自定义登录URI的操作,登录成功之后,我们再次访问后端中的API的时候要在请求头中携带token,此时的token是jwt字符串,我们需要将该jwt字符串进行解析,查看解析后的User对象是否处于登录状态.登录状态下,将…

《UNIX网络编程卷1:套接字联网API》第1章 简介

《UNIX网络编程卷1&#xff1a;套接字联网API》第1章 简介 1.1 网络编程的核心价值与挑战 网络编程是实现跨设备通信的技术基础&#xff0c;其核心目标是通过协议栈实现数据的可靠传输与高效交换。在嵌入式系统、云计算、物联网等领域&#xff0c;网络编程能力直接决定了系统的…

D-Wave专用量子计算机登顶Science 率先展示在真实场景中的量子优势(内附下载)

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨浪味仙 行业动向&#xff1a;4200字丨16分钟阅读 摘要&#xff1a;加拿大专用量子计算机公司 D-Wave 在 Science 期刊发表了论文&#xff0c;题为《Beyond-Classical Compu…

在Ubuntu上安装MEAN Stack的4个步骤

在Ubuntu上安装MEAN Stack的4个步骤为&#xff1a;1.安装MEAN&#xff1b;2.安装MongoDB&#xff1b;3.安装NodeJS&#xff0c;Git和NPM&#xff1b;4.安装剩余的依赖项。 什么是MEAN Stack&#xff1f; 平均堆栈一直在很大程度上升高为基于稳健的基于JavaScript的开发堆栈。…

jmeter将返回的数据写入csv文件

举例说明&#xff0c;我需要接口返回体中的exampleid与todoid的数据信息&#xff08;使用边界提取器先将其提取&#xff09;&#xff0c;并将其写入csv文件进行保存 使用后置处理器BeanShell 脚本实例如下 import java.io.*;// 设置要写入的文件路径 String filePath "…

Linux下Redis哨兵集群模式搭建(1主2从+3哨兵)

Linux下Redis哨兵集群模式搭建&#xff08;1主2从3哨兵&#xff09; 一、Redis哨兵模式搭建 1.安装包下载 链接: https://pan.baidu.com/s/1_n2rCMi5MHX-mVkkyMo4LA 提取码: gbra 2.新建redis目录 mkdir -p /app/redis3.解压到/app/redis目录下 tar -zxvf redis-6.2.16.ta…

Debian 系统命令集合 |Debian 和 CentOS常见命令的异同

Debian 系统命令集合 Debian 是一个非常流行且稳定的 Linux 发行版&#xff0c;广泛用于服务器、桌面和工作站环境。 Debian 和 CentOS常见命令 使用方式的对比 注: 部分人&#xff08;比如我&#xff09;先学的centos&#xff0c;其实centos和debian 就记住几十个有区别命…