【C++ 11多线程加速计算实操教程】

【C++ 11多线程加速计算实操教程】

  • 1. 了解线程的基本概念
  • 2. 创建线程
    • 2.1 启动线程的基本示例:
    • 2.2 运行结果
  • 3. 线程加速计算
    • 3.1 演示如何使用多个线程计算数组的和:
    • 3.2 运行结果
    • 3.3 结果分析
    • 3.4 拓展学习
  • 4. 互斥量(Mutex)
    • 4.1 演示如何使用互斥量来保护共享变量:
    • 4.2 运行结果
    • 4.3 结果分析
  • 5. 传递参数
    • 5.1 线程参数传递
    • 5.2 运行结果
  • 6. 线程池
    • 6.1 线程池的基本功能
    • 6.2. 线程池的简单实现
    • 6.3 运行结果
    • 6.4 输出结果分析
  • 6. 总结

学习 C++ 多线程编程是一个非常重要和实用的领域,尤其是在现代软件开发中。以下是有关如何从基础逐步学习 C++ 线程的详细步骤和建议。从创建线程,线程加速计算,互斥量,参数传递和线程池管理循序渐进,接下来开始第一步了解线程。
环境搭建参考:

  1. 【Qt安装与简易串口控制Arduino开发板小灯教程】
  2. 【VS2019安装+QT配置】

此教程采用Fitten code插件交互生成的,大家都快来试试吧!😘😘😘https://codewebchat.fittenlab.cn/?share=2024923_47cpt62du

1. 了解线程的基本概念

在开始多线程编程之前,了解以下基本概念是必要的:

  • 线程是什么:线程是系统能够独立运行的最小单位,一个进程可以包含多个线程。线程共享进程的资源,如内存和文件句柄。
  • 优点:多线程可以提高程序的执行效率,特别是在多核处理器上,可以并行处理多个任务。
  • 缺点:线程间的共享资源可能导致数据竞争和死锁等问题,编程复杂性增加。

2. 创建线程

在 C++11 及以后的版本中,可以使用标准库中的 std::thread 类来创建和管理线程。以下是创建和

2.1 启动线程的基本示例:

#include <iostream>
#include <thread>void threadFunction() {std::cout << "Hello from thread!" << std::endl;
}int main() {// 创建线程std::thread t(threadFunction);// 等待线程完成t.join();std::cout << "Thread has finished execution." << std::endl;return 0;
}

2.2 运行结果

在这里插入图片描述
在这个示例中,函数 threadFunction 被作为线程执行的任务,然后在主线程中调用 join(),用于等待线程执行完毕。

3. 线程加速计算

线程可以用来加速计算,例如,将一个大的计算任务分解成多个子任务,分别在不同的线程中执行。下面是一个简单的示例,

3.1 演示如何使用多个线程计算数组的和:

#include <iostream>
#include <vector>
#include <thread>
#include <chrono> // 用于计时// 使用多线程计算和的函数
void sum(const std::vector<int>& numbers, unsigned long& result, size_t start, size_t end) {long long localResult = 0; // 使用局部变量来减少主线程中的地址频繁变化for (size_t i = start; i < end; ++i) {localResult += numbers[i];}result = localResult; // 将局部结果赋值给结果引用
}// 不使用线程计算和的函数
unsigned long sumSerial(const std::vector<int>& numbers) {unsigned long result = 0;for (size_t i = 0; i < numbers.size(); ++i) {result += numbers[i];}return result;
}int main() {const size_t arraySize = 40000000; // 扩大数组大小const int numThreads = 4; // 线程数量std::vector<int> numbers(arraySize, 9); // 初始化一个包含40000000个9的数组std::vector<std::thread> threads(numThreads);std::vector<unsigned long> results(numThreads, 0); // 存储每个线程的结果// 使用多线程计算auto startTime = std::chrono::high_resolution_clock::now(); // 记录开始时间// 批量生成线程for (int i = 0; i < numThreads; ++i) {size_t start = i * (arraySize / numThreads);size_t end = (i + 1) * (arraySize / numThreads);threads[i] = std::thread(sum, std::ref(numbers), std::ref(results[i]), start, end);}// 等待所有线程完成for (int i = 0; i < numThreads; ++i) {threads[i].join();}auto endTime = std::chrono::high_resolution_clock::now(); // 记录结束时间std::chrono::duration<double> elapsedSeconds = endTime - startTime; // 计算花费的时间unsigned long totalParallel = 0;for (const auto& result : results) {totalParallel += result; // 合并所有线程的结果}std::cout << "Total sum (multithreading): " << totalParallel << std::endl;std::cout << "Time consumed (multithreading): " << elapsedSeconds.count() << " seconds" << std::endl;// 不使用线程计算startTime = std::chrono::high_resolution_clock::now(); // 记录开始时间unsigned long totalSerial = sumSerial(numbers);endTime = std::chrono::high_resolution_clock::now(); // 记录结束时间elapsedSeconds = endTime - startTime; // 计算花费的时间std::cout << "Total sum (single-thread): " << totalSerial << std::endl;std::cout << "Time consumed (single-thread): " << elapsedSeconds.count() << " seconds" << std::endl;return 0;
}

代码说明

  • sumSerial 函数:这个函数用于不使用多线程的情况下计算数组元素的和。它简单地遍历整个数组,依次相加。
  • 计时功能:在计算和的地方,使用 std::chrono 库来记录开始和结束时间,以便测量每种方法的执行时间。
  • 主函数:首先进行多线程计算,并记录执行时间。然后进行单线程计算,并同样记录执行时间。最后输出两次计算的结果和所花费的时间。

3.2 运行结果

在这里插入图片描述
运行效果
执行这段代码后,您将能看到多线程计算与单线程计算的总和以及各自所花费的时间。这有助于直观地比较性能,观察多线程带来的加速效果。加速后比加速前快了4倍

3.3 结果分析

  • 亮点设计:通过局部变量localResult代替全局变量result频繁变化。

  • 批量生成线程:使用一个循环来创建多个线程,每个线程负责计算数组的一部分,计算的起始与结束位置由 start 和 end 变量确定。

  • 结果存储:在一个 results 向量中存储每个线程的计算结果,便于最后进行总和操作。

  • 清晰简洁:通过这种方式,若需更改线程数量,只需更改 numThreads 常量的值,代码就会自动适应。

3.4 拓展学习

运行这个程序后,您将能够生成一个大的随机数组,并通过多线程与单线程的方法分别求和,输出结果和时间消耗。

#include <iostream>
#include <vector>
#include <thread>
#include <cstdlib>
#include <ctime>// 定义随机数组的行和列
#define ROW   5
#define COL 10000000// 获得随机数组
int** getRandom() {std::cout << "生成" << ROW << "X" << COL << "的随机数组" << std::endl;int** r;r = (int**)malloc(ROW * sizeof(int*));for (int i = 0; i < ROW; i++) {r[i] = (int*)malloc(COL * sizeof(int));}srand(static_cast<unsigned>(time(NULL)));for (int i = 0; i < ROW; i++) {for (int j = 0; j < COL; ++j) {r[i][j] = rand();}}std::cout << "随机数组生成完毕\n\n";return r;
}// 定义传入线程的结构体
struct TagValue {int* arr;long long sum;
};// 子线程调用求和方法
void threadSum(TagValue* v1) {// 子线程的求和方法TagValue *v = (TagValue*)v1;int* arr = v->arr;long long sum = 0;for (int i = 0; i < COL; i++) {sum += *(arr + i);}v->sum = sum;
}// 多线程主方法
void testThreadTime(int** r) {// 计算多线程运行时间std::vector<TagValue> res(ROW);std::vector<std::thread> ths(ROW);auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < ROW; i++) {res[i].arr = r[i];res[i].sum = 0;// 开启子线程ths[i] = std::thread(threadSum, &res[i]);std::cout << "子线程" << i << "创建成功" << std::endl;}// 等待所有线程完成for (auto& th : ths) {th.join();}// 打印返回值std::cout << "多线程的执行结果是:" << std::endl;for (int i = 0; i < ROW; i++) {std::cout << res[i].sum << std::endl;}std::cout << "主线程执行完毕" << std::endl;auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> elapsed = end - start;std::cout << "Time Used: " << elapsed.count() << " seconds" << std::endl;
}// 非多线程对照方法
void testNoThreadTime(int** r) {auto start = std::chrono::high_resolution_clock::now();long long res[ROW] = {0};for (int i = 0; i < ROW; i++) {for (int j = 0; j < COL; j++) {res[i] += r[i][j];}}auto end = std::chrono::high_resolution_clock::now();std::cout << "对照组执行结果是:" << std::endl;for (int i = 0; i < ROW; i++) {std::cout << res[i] << std::endl;}std::chrono::duration<double> elapsed = end - start;std::cout << "Time Used: " << elapsed.count() << " seconds" << std::endl;
}int main() {int** r = getRandom();testThreadTime(r);testNoThreadTime(r);getchar();// 释放动态分配的内存for (int i = 0; i < ROW; i++) {free(r[i]);}free(r);return 0;
}
  • 使用 std::thread:在这个修改后的版本中,生成线程的部分使用 std::thread 来创建线程并启动,threadSum 函数,每个线程的参数通过指向 TagValue 结构体的指针传递。
  • 时钟的使用:使用 std::chrono::high_resolution_clock 来测量执行时间,使用 std::chrono::duration 计算时间差。
  • 内存管理:在 main 函数末尾,释放通过 malloc 动态分配的内存,以避免内存泄漏。
  • 输出信息:在控制台输出生成随机数组和各个线程的执行结果,提供清晰的输出格式。

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

在C/C++的多线程使用过程中,一定要注意在子线程中对传入地址的写操作。频繁的跨线程写操作,会带来效率的大幅降低。

4. 互斥量(Mutex)

互斥量(std::mutex)是 C++ 中用于保护共享资源的一种同步机制,确保在任何时刻只有一个线程可以访问这些共享资源。这可以帮助避免数据竞争问题。

示例:使用互斥量防止数据竞争
以下是一个示例,

4.1 演示如何使用互斥量来保护共享变量:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex> // 包含互斥量库
#include <chrono>unsigned long sharedCounterNoMutex = 0; // 不使用互斥量的共享变量
unsigned long sharedCounterMutex = 0; // 使用互斥量的共享变量
std::mutex mtx; // 创建一个互斥量// 使用互斥量的计数函数
void incrementCounterWithMutex(int iterations) {for (int i = 0; i < iterations; ++i) {std::lock_guard<std::mutex> lock(mtx); // 锁定互斥量++sharedCounterMutex; // 访问共享变量}
}// 不使用互斥量的计数函数
void incrementCounterNoMutex(int iterations) {for (int i = 0; i < iterations; ++i) {++sharedCounterNoMutex; // 直接访问共享变量}
}int main() {const int numThreads = 4; // 线程数量const int iterations = 100000; // 每个线程要执行的迭代次数std::vector<std::thread> threads;// 使用互斥量的实验auto startTimeMutex = std::chrono::high_resolution_clock::now();for (int i = 0; i < numThreads; ++i) {threads.emplace_back(incrementCounterWithMutex, iterations);}for (auto& thread : threads) {thread.join(); // 等待所有线程完成}auto endTimeMutex = std::chrono::high_resolution_clock::now();std::chrono::duration<double> elapsedSecondsMutex = endTimeMutex - startTimeMutex;std::cout << "Final counter value with mutex: " << sharedCounterMutex << std::endl;std::cout << "Time consumed with mutex: " << elapsedSecondsMutex.count() << " seconds" << std::endl;// 清空线程向量threads.clear();// 不使用互斥量的实验auto startTimeNoMutex = std::chrono::high_resolution_clock::now();for (int i = 0; i < numThreads; ++i) {threads.emplace_back(incrementCounterNoMutex, iterations);}for (auto& thread : threads) {thread.join(); // 等待所有线程完成}auto endTimeNoMutex = std::chrono::high_resolution_clock::now();std::chrono::duration<double> elapsedSecondsNoMutex = endTimeNoMutex - startTimeNoMutex;std::cout << "Final counter value without mutex: " << sharedCounterNoMutex << std::endl;std::cout << "Time consumed without mutex: " << elapsedSecondsNoMutex.count() << " seconds" << std::endl;return 0;
}

互斥量示例:

  • std::mutex:创建一个互斥量 mtx,用于保护共享变量 sharedCounter。
  • std::lock_guard:用于自动管理互斥量的锁,离开作用域时自动释放锁,避免手动解锁的风险。

4.2 运行结果

在这里插入图片描述

使用互斥量可以保护共享资源,防止数据竞争,而信号量则可以控制对资源的并发访问。二者都是实现线程安全的重要工具。

4.3 结果分析

结果概述:

  • 使用互斥量的计数器值:
    Final counter value with mutex: 400000
    不使用互斥量的计数器值:
    Final counter value without mutex: 149482
  • 时间消耗:
    Time consumed with mutex: 0.0350588 seconds
    Time consumed without mutex: 0.0029207 seconds
  • 互斥量的影响:使用互斥量的计数器值为 400,000,代表了每个线程在执行过程中正确地增加了计数值。互斥量的作用是确保在同一时刻只有一个线程能够修改 sharedCounterMutex,所以最终结果是稳定和准确的。不使用互斥量的计数器值为 149482,明显低于预期的 400,000(4个线程各自增加 100,000)。这表明在多个线程同时访问 sharedCounterNoMutex 时,发生了数据竞争,导致一些增量操作被覆盖或丢失了,最终结果是不准确的。
  • 时间消耗分析:使用互斥量的时间为 0.0350588 秒,尽管由于互斥量的锁定与解锁操作,存在一定的开销,但它确保了程序的线程安全。不使用互斥量的时间为 0.0029207 秒,显著低于使用互斥量的时间。没有锁信息的处理成本使得操作更快速,但缺乏安全性。
  • 准确性 vs. 性能:
    使用互斥量确保了数据的一致性和准确性,避免了线程间的数据竞争问题。尽管这带来了更高的处理时间,但结果是可靠的。不使用互斥量可以提高性能和效率,但会严重影响结果的准确性,尤其是在多线程操作共享数据时。
  • 应用场景:
    当操作共享数据时,如果希望确保结果的准确性和一致性,应该使用互斥量。若对性能要求较高且对数据不一致的容忍度较大,可以考虑不使用互斥量,但需评估数据丢失的风险。这次实验清晰地展示了在多线程编程中,使用互斥量的必要性与对比效果。

5. 传递参数

在 C++ 中,将参数传递给线程任务是一个常见且重要的操作,尤其是在多线程编程中。通过适当的参数传递,可以使线程执行特定的任务。

  • 值传递:将参数按值传递给线程。这意味着线程将获得参数的副本,线程内对参数的修改不会影响主线程中的变量。
  • 引用传递:通过引用传递参数。这样可以让线程访问主线程的变量,任何对这些变量的修改都将反映在主线程中。
  • 指针传递:将参数按指针类型传递。这与引用类似,允许线程访问和修改主线程中的变量。
    下面是一个简单的示例,展示如何将参数通过值、引用和指针传递给线程任务。

下面是一个简单的示例,展示如何将参数通过值、引用和指针传递给线程任务。

5.1 线程参数传递

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>// 通过值传递参数
void taskByValue(int value) {value += 10; // 修改副本std::cout << "Value in task (by value): " << value << std::endl;
}// 通过引用传递参数
void taskByReference(int& value) {value += 10; // 修改原变量std::cout << "Value in task (by reference): " << value << std::endl;
}// 通过指针传递参数
void taskByPointer(int* value) {if (value) { // 防止空指针*value += 10; // 修改原变量std::cout << "Value in task (by pointer): " << *value << std::endl;}
}int main() {int num = 5;// 创建线程,传递参数std::cout << "Original num: " << num << std::endl;// 通过值传递std::thread t1(taskByValue, num);t1.join();// 通过引用传递std::thread t2(taskByReference, std::ref(num));t2.join();// 通过指针传递std::thread t3(taskByPointer, &num);t3.join();std::cout << "Final num after all tasks: " << num << std::endl;return 0;
}

代码说明

  • 值传递(taskByValue):taskByValue 函数接收一个整数参数的副本。在函数内部对该副本的修改不会影响主线程中的num 变量。
  • 引用传递(taskByReference):taskByReference函数接收一个整数的引用。这意味着对它的修改直接反映在主线程中的 num 变量上。
  • 指针传递(taskByPointer):taskByPointer 函数接收一个整数的指针,允许直接修改主线程中的变量。

5.2 运行结果

在这里插入图片描述
运行代码后,您将观察到:

  • 通过值传递,输出的值不会影响原变量。
  • 通过引用和指针传递,输出的值可以修改原变量。

这个示例清晰地展示了如何将参数传递给线程任务,不同的传递方式适用于不同的情况和需求。通过了解这些基础,您可以方便地设计多线程程序。

线程池是现代多线程编程中的一个重要概念,用于有效管理线程的生命周期以及重复利用线程,以避免频繁创建和销毁线程带来的开销。在实际应用中,线程池可以提高性能并优化资源利用。

6. 线程池

线程池是一种设计模式,它维护一定数量的线程,准备好去执行特定的任务。任务可以被添加到任务队列中,线程池中的线程会从队列中取出任务并执行。这种方式可以大幅降低线程的创建和销毁时间,同时也能有效控制并发数量。

6.1 线程池的基本功能

任务提交:提供一个接口供用户提交任务。
线程管理:管理线程的生命周期,保持一定数量的线程在空闲状态,等待任务执行。
任务队列:存储待执行的任务,确保任务的有序执行。

6.2. 线程池的简单实现

下面是一个简单的线程池实现示例,使用 C++11 标准库中的线程和互斥量。

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>class ThreadPool {
public:ThreadPool(size_t numThreads);~ThreadPool();// 提交任务void enqueue(std::function<void()> task);private:std::vector<std::thread> workers;         // 工作线程std::queue<std::function<void()>> tasks;  // 任务队列std::mutex queueMutex;                     // 任务队列的互斥量std::condition_variable condition;          // 条件变量std::atomic<bool> stop;                    // 停止标记void worker(); // 工作线程的执行函数
};// 构造函数
ThreadPool::ThreadPool(size_t numThreads) : stop(false) {for (size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this] { worker(); }); // 创建工作线程}
}// 析构函数
ThreadPool::~ThreadPool() {stop = true;condition.notify_all(); // 唤醒所有线程for (std::thread &worker : workers) {worker.join(); // 等待所有线程完成}
}// 工作线程的执行函数
void ThreadPool::worker() {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queueMutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) return; // 如果停止且任务队列为空,退出线程task = std::move(tasks.front());tasks.pop();}task(); // 执行任务}
}// 提交任务
void ThreadPool::enqueue(std::function<void()> task) {{std::unique_lock<std::mutex> lock(queueMutex);tasks.emplace(std::move(task)); // 将任务添加到队列}condition.notify_one(); // 唤醒一个线程
}// 示例任务
void exampleTask(int id) {std::cout << "Task " << id << " is being processed by thread " << std::this_thread::get_id() << std::endl;
}int main() {ThreadPool pool(4); // 创建一个线程池,包含4个工作线程// 提交任务for (int i = 1; i <= 10; ++i) {pool.enqueue([i] { exampleTask(i); });}// 等待一段时间以确保所有任务完成std::this_thread::sleep_for(std::chrono::seconds(1));return 0;
}

代码说明

  • ThreadPool 类: 使用 std::vectorstd::thread 存储工作线程。 使用 std::queue<std::function<void()>> 存储待执行任务。 使用 std::mutex 和
    std::condition_variable 来管理任务队列的线程安全和任务执行。
  • 构造函数: 创建指定数量的工作线程,调用 worker 函数。
  • 析构函数: 设置停止标记,将所有线程唤醒并等待它们完成。
  • worker 函数: 无限循环,等待获取任务并执行,直到收到停止信号并且任务队列为空。
  • enqueue 函数: 允许用户提交任务到任务队列。
  • 示例任务: exampleTask 函数是一个简单的示例任务,打印其 ID 和执行线程的 ID。

6.3 运行结果

在这里插入图片描述

6.4 输出结果分析

  • 多线程并发:输出中有多个任务并行执行,这表明线程池成功地利用了多个工作线程来处理任务。不同的任务在不同的线程中被处理,这是线程池设计的目标。
  • 线程共享:观察到任务由同一个线程多次处理(如 Task 5 和 Task 6 都由线程 5240 处理)。这说明线程在执行完任务后并没有闲置,而是继续从任务队列中取出新的任务,表明线程池在高效运行。
  • 任务调度:由于任务是按照提交的顺序放入队列并由工作线程并行处理,因此可以看到任务的顺序输出并不一致。某些较早提交的任务可能在线程上完成的时间较晚(如 Task 9 和 Task 10),这可能与任务的执行时间、线程的调度方式以及任务队列的管理有关。
  • 线程标识:每个输出中的 thread [ID] 表示处理该任务的具体线程 ID。根据输出,可以看到任务分配不是均匀的,某些线程处理了更多的任务。这是因为工作线程在完成当前任务后会立刻获取下一个任务进行处理,而某些线程可能在处理特定任务时执行速度更快。

6. 总结

🥳🥳🥳现在,我们在本教程中,您学习了从创建线程,线程加速计算,互斥量,参数传递和线程池管理教程。🛹🛹🛹从而实现对外部世界进行感知,充分认识这个有机与无机的环境🥳🥳🥳科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。🤣🤣🤣

如果你有任何问题,可以通过下面的二维码加入鹏鹏小分队,期待与你思维的碰撞😘😘😘

参考文献:

  1. 【Qt安装与简易串口控制Arduino开发板小灯教程】
  2. 【VS2019安装+QT配置】
  3. 记录一个使C/C++多线程无法加速计算的问题

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【GUI设计】基于图像边缘提取的GUI系统(5),matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于图像边缘提取的GUI系统&#xff08;5&#xff09;&#xff0c;用matlab实现。 本…

HTML、CSS

初识web前端 web标准 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C (World Wide Web Consortium&#xff0c;万维网联盟) 负责制定。三个组成部分: HTML: 负责网页的结构(页面元素和内容)。CSS: 负责网页的表现(页面元素的外观、位置等页面样…

荣耀手机AI搜索革新体验:一键总结归纳,让信息获取更高效

在信息爆炸的时代&#xff0c;我们每天都被海量的数据包围&#xff0c;如何快速、准确地获取所需信息成为了现代人的一大挑战。 近日&#xff0c;荣耀手机宣布其AI搜索功能正式上线&#xff0c;这一创新举措不仅为使用者带来了前所未有的便捷体验&#xff0c;更在智能手机领域…

博图软件项目创建

目录 一、新建项目 二、IP设置具体的版本号查看方法 ​三、电脑IP设置如下 一、新建项目 1、双击打开软件 2、创建新项目&#xff0c;路径可以自己选择&#xff0c;填写相关信息&#xff0c;点击创建 3、选择组态设备 4、选择对应的的设备型号&#xff0c;点击添加 5、密…

【刷题2—滑动窗口】最大连续1的个数lll、将x减到0的最小操作数

目录 一、最大连续1的个数lll二、将x减到0的最小操作数 一、最大连续1的个数lll 题目&#xff1a; 思路&#xff1a; 问题转换为&#xff1a;找到一个最长子数组&#xff0c;这个数组里面0的个数不能超过k个 定义一个变量count&#xff0c;来记录0的个数&#xff0c;进窗口、…

Minio上传url资源文件,文件内容不全的问题

遇到问题 使用minio-client时候上传文件为url链接时候&#xff0c;上传inputstream流出现了文件上传成功&#xff0c;但是文件内容缺失&#xff0c;无法正常打开&#xff01; 先看看基本的依赖和配置代码&#xff1a; pom.xml依赖 <!-- tika MIME检测机制 --><depen…

JVM(HotSpot):程序计数器(Program Counter Register)

文章目录 一、内存结构图二、案例解读三、工作流程四、特点 一、内存结构图 二、案例解读 我们使用javap对字节码进行反编译&#xff0c;来看下程序计数器怎么体现的。 IDEA写一个简单的Java代码 反编译命令 javap -verbose InitTest.class $ javap -verbose InitTest.clas…

【AIGC】ChatGPT RAG提取文档内容,高效制作PPT、论文

目录 一、理解 RAG 技术 二、利用 ChatGPT 的 RAG 技术提取文档内容 三、高效制作 PPT 四、高效撰写论文 五、最佳实践与建议 六、工具推荐 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;利用先进的技术工具如 ChatGPT 的 RAG&#xff08;Ret…

R包安装教程,如何安装rjags和infercnv

一.介绍 在数据分析过程中&#xff0c;R语言因其强大的统计分析能力和丰富的包生态系统&#xff0c;成为众多研究人员和数据科学家的首选工具。本文将详细介绍如何在R环境中安装两个重要的R包——rjags和infercnv。rjags用于与JAGS&#xff08;Just Another Gibbs Sampler&…

基于 Amazon Bedrock +lambda函数调用大模型构建你的智能网页助手

​ 文章目录 1. 前言2. 使用到的关键产品2.1 Amazon Bedrock2.2 Amazon lambda2.3 Amazon API Gateway 3. 注册亚马逊云科技账号4. 构建大模型API4.1 调用 Amazon Bedrock4.2 使用 Amazon lambda函数4.3 调Amazon API Gateway 5. 构建应用调用API6. 总结 1. 前言 传统的大模型…

LabVIEW界面输入值设为默认值

在LabVIEW中&#xff0c;将前面板上所有控件的当前输入值设为默认值&#xff0c;可以通过以下步骤实现&#xff1a; 使用控件属性节点&#xff1a;你可以创建一个属性节点来获取所有控件的引用。 右键点击控件&#xff0c;选择“创建” > “属性节点”。 设置属性节点为“D…

怎么用gitee做一个图片仓库,在md文档中用这个图片网络地址,然后显示图片

痛因&#xff1a;我为什么要这样做&#xff0c;呃&#xff0c;我一开始图片都是存本地地址的&#xff0c;放在和这个md文档同级的assets文件夹下面&#xff0c;这样子确实当时很方便&#xff0c;复制粘贴什么也不用管&#xff0c;但是想把这个文档分享给别的人的时候&#xff0…

【有啥问啥】多臂老虎机(Multi-Armed Bandit,MAB)算法详解

多臂老虎机&#xff08;Multi-Armed Bandit&#xff0c;MAB&#xff09;算法详解 1. 引言 多臂老虎机&#xff08;Multi-Armed Bandit&#xff0c;MAB&#xff09;问题源自概率论和决策论&#xff0c;是一个经典的决策优化问题。最早提出的形式是赌场中的老虎机问题&#xff…

在线秘密基地--性能测试

根据之前的测试报告中的测试用例使用jmeter进行性能测试&#xff08;在性能测试之前&#xff0c;应先进行功能测试&#xff09;。 测试报告----功能测试_功能测试报告-CSDN博客https://blog.csdn.net/m0_74876421/article/details/141307905一、使用jmeter进行功能测试 可查看…

HDFS分布式文件系统01-HDFS架构与SHELL操作

HDFS分布式文件系统 学习目标第一课时知识点1-文件系统的分类单机文件系统网络文件系统分布式文件系统 知识点2-HDFS架构知识点3-HDFS的特点知识点4-HDFS的文件读写流程知识点5-HDFS的健壮性 第二课时知识点1-HDFS的Shell介绍HDFS Shell的语法格式如下。HDFS Shell客户端命令中…

STM32 软件触发ADC采集

0.91寸OLED屏幕大小的音频频谱&#xff0c;炫酷&#xff01; STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系&#xff08;深度理解&#xff09; STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能&#xff0c;几乎任何一款单片机都会包含这个功能&a…

信息安全工程师(13)网络攻击一般过程

前言 网络攻击的一般过程是一个复杂且系统化的行为&#xff0c;其目标往往在于未经授权地访问、破坏或窃取目标系统的信息。 一、侦查与信息收集阶段 开放源情报收集&#xff1a;攻击者首先会通过搜索引擎、社交媒体、论坛等公开渠道获取目标的基本信息&#xff0c;如姓名、地址…

Pytest-如何将allure报告发布至公司内网

原理简介 使用Python启动HTTP服务器&#xff0c;指定一个端口号port&#xff0c;内网用户可以使用ipport访问报告。 本文章继续进阶&#xff0c;简单使用nginx进行一个代理&#xff0c;使用域名可以直接访问报告。 前情概述 Pytest-allure如何在测试完成后自动生成完整报告&am…

Axure大屏可视化模板:跨领域数据分析平台原型案例

随着信息技术的飞速发展&#xff0c;数据可视化已成为各行各业提升管理效率、优化决策过程的重要手段。Axure作为一款强大的原型设计工具&#xff0c;其大屏可视化模板在农业、园区、城市、企业数据可视化、医疗等多个领域得到了广泛应用。本文将通过几个具体案例&#xff0c;展…

生成PPT时支持上传本地的PPT模板了!

制作 PPT 时想要使用特定的 PPT 模板&#xff1f; 现在&#xff0c;歌者 PPT 的「自定义模板功能」已全面升级&#xff01;你可以轻松上传自己的本地 PPT 模板&#xff0c;无论是公司统一风格的模板&#xff0c;还是带有个人设计风格的模板&#xff0c;都能无缝导入歌者 PPT。…