COMATE插件实现使用线程池高级并发模型简化多线程编程

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本文介绍了COMATE插件的使用,该插件通过线程池实现高级并发模型,简化了多线程编程的过程,并提供了生成结果和代码参考。

一 生成结果

二 代码参考



/**
 * 在DICOM中,元数据是以标签(Tag)的形式存储的,每个标签都有一个唯一的组号(Group Number)和元素号(Element Number)。这些标签用于标识DICOM数据集中的各种信息,如患者信息、图像信息、设备信息等。

如果您想提取DICOM数据集中的其他元数据标签,并将它们写入CSV文件,您可以使用DCMTK库中的DcmDataset类来访问这些标签。以下是一个扩展的示例,演示了如何提取并写入一些常见的DICOM元数据标签:
*/

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <dcmtk/dcmdata/dcfilefo.h>

int main() {
   
    // 假设您有一个包含DICOM数据的内存缓冲区
    unsigned char* dicomBuffer = nullptr;
    size_t bufferSize = 0;

    // 加载DICOM数据到DcmFileFormat对象中
    DcmFileFormat fileFormat;
    if (fileFormat.loadFileFromMemory(dicomBuffer, bufferSize) != EC_Normal) {
   
        std::cerr << "Error loading DICOM data from memory." << std::endl;
        return 1;
    }

    // 获取数据集
    DcmDataset* dataset = fileFormat.getDataset();
    if (dataset == nullptr) {
   
        std::cerr << "Error getting DICOM dataset." << std::endl;
        return 1;
    }

    // 打开CSV文件
    std::ofstream csvFile("dicom_metadata.csv");
    if (!csvFile.is_open()) {
   
        std::cerr << "Error opening CSV file." << std::endl;
        return 1;
    }

    // 写入CSV文件标题行
    csvFile << "Tag,Value" << std::endl;

    // 提取并写入一些常见的DICOM元数据标签
    const DcmTag patientNameTag(0x0010, 0x0010); // 患者姓名
    const DcmTag patientIDTag(0x0010, 0x0020);   // 患者ID
    const DcmTag modalityTag(0x0008, 0x0060);    // 成像模态
    const DcmTag imageDateTag(0x0008, 0x0018);   // 图像日期
    const DcmTag imageTimeTag(0x0008, 0x0019);   // 图像时间

    writeTagToCSV(csvFile, dataset, patientNameTag, "Patient Name");
    writeTagToCSV(csvFile, dataset, patientIDTag, "Patient ID");
    writeTagToCSV(csvFile, dataset, modalityTag, "Modality");
    writeTagToCSV(csvFile, dataset, imageDateTag, "Image Date");
    writeTagToCSV(csvFile, dataset, imageTimeTag, "Image Time");

    // 关闭CSV文件
    csvFile.close();

    return 0;
}

// 辅助函数,用于将DICOM标签的值写入CSV文件
void writeTagToCSV(std::ofstream& csvFile, DcmDataset* dataset, const DcmTag& tag, const std::string& columnName) {
   
    DcmElement element;
    if (dataset->findAndGetElement(tag, element).good()) {
   
        std::string value;
        switch (element.getTag().getEVR()) {
   
            case EVR_AE:
            case EVR_AS:
            case EVR_CS:
            case EVR_DA:
            case EVR_DS:
            case EVR_DT:
            case EVR_LO:
            case EVR_LT:
            case EVR_PN:
            case EVR_SH:
            case EVR_ST:
            case EVR_UI:
            case EVR_UR:
                value = element.getValue().getOFString();
                break;
            case EVR_FD:
            case EVR_FL:
                value = std::to_string(element.getValue().getFloat64());
                break;
            case EVR_IS:
                value = std::to_string(elementgetValue.().getInt32sList().at(0));
                break;_
            ULcase: EVR
                _valueUS =: std::
                value = std::to_string(element.getValue().getUint16());
                break;
            case EVRto_string(element.getValue().getUint32());
                break;
            case EVR_SL:
                value = std::to_string(element.getValue().getInt32());
                break;
            case EVR_SS:
                value = std::to_string(element.getValue().getInt16());
                break;
            case EVR_SEQ:
                // 序列需要递归处理,这里简化处理
                value = "Sequence";
                break;
            default:
                value = "Unknown Type";
                break;
        }
        // 写入CSV文件
        csvFile << tag.getGroup() << "," << tag.getElement() << "," << columnName << "," << value << std::endl;
    } else {
   
        // 标签未找到,写入错误信息
        csvFile << "0000,0000,Error," << "Tag not found" << std::endl;
    }
}
/**
 * 现在,writeTagToCSV函数已经准备好,可以根据DICOM标签的类型将其值转换为字符串,并写入CSV文件。在调用writeTagToCSV时,我们需要传入CSV文件流、数据集指针、要提取的标签以及一个描述该标签的列名。

最后,让我们回到main函数中,并使用这个辅助函数来提取和写入更多的元数据标签:
*/
int main() {
   
    // ... (省略了加载DICOM数据和打开CSV文件的代码)

    // 提取并写入更多的DICOM元数据标签
    const DcmTag patientNameTag(0x0010, 0x0010); // 患者姓名
    const DcmTag patientIDTag(0x0010, 0x0020);   // 患者ID
    const DcmTag patientSexTag(0x0010, 0x0040);  // 患者性别
    const DcmTag patientBirthDateTag(0x0010, 0x0030); // 患者出生日期
    const DcmTag modalityTag(0x0008, 0x0060);    // 成像模态
    const DcmTag imageDateTag(0x0008, 0x0018);   // 图像日期
    const DcmTag imageTimeTag(0x0008, 0x0019);   // 图像时间

    // 写入CSV文件标题行(如果尚未写入)
    if (!csvFile.is_open()) {
   
        csvFile.open("dicom_metadata.csv");
        csvFile << "Tag,Value" << std::endl;
    }

    // 写入患者信息
    writeTagToCSV(csvFile, dataset, patientNameTag, "Patient Name");
    writeTagToCSV(csvFile, dataset, patientIDTag, "Patient ID");
    writeTagToCSV(csvFile, dataset, patientSexTag, "Patient Sex");
    writeTagToCSV(csvFile, dataset, patientBirthDateTag, "Patient Birth Date");

    // 写入图像信息
    writeTagToCSV(csvFile, dataset, modalityTag, "Modality");
    writeTagToCSV(csvFile, dataset, imageDateTag, "Image Date");
    writeTagToCSV(csvFile, dataset, imageTimeTag, "Image Time");

    // 关闭CSV文件
    csvFile.close();

    return 0;
}






/**
 * 使用线程池(std::thread::hardware_concurrency() 或第三方库如 Intel Threading Building Blocks (TBB))可以进一步简化多线程编程,因为它负责管理线程的创建、销毁和调度,从而减少了程序员的负担,并提高了性能。线程池能够复用已经创建的线程,避免了频繁创建和销毁线程带来的开销。

在C++11及更高版本中,我们可以使用std::thread::hardware_concurrency()来获取系统可用的逻辑处理器数量,然后创建相应数量的线程来执行任务。不过,C++标准库并没有直接提供线程池的实现,因此你可能需要自己实现一个简单的线程池,或者使用第三方库。

下面是一个使用C++11和std::thread::hardware_concurrency()的简单线程池实现示例:
*/
#include <vector>
#include <queue>
#include <thread>
#include <future>
#include <functional>
#include <stdexcept>

class ThreadPool {
   
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    // need to keep track of threads so we can join them
    std::vector< std::thread > workers;
    // the task queue
    std::queue< std::function<void()> > tasks;

    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
    :   stop(false)
{
   
    for(size_t i = 0;i<threads;++i) {
   
        workers.emplace_back(
            [this] {
   
                for(;;) {
   
                    std::function<void()> task;

                    {
   
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{
    return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty()) {
   
                            return;
                        }
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }

                    task();
                }
            }
        );
    }
}

// add new work item to the pool
template<class F, class... 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;

    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);

        // don't allow enqueueing after stopping the pool
        if(stop) {
   
            throw std::runtime_error("enqueue on stopped ThreadPool");
        }

        tasks.emplace([task](){
    (*task)(); });
    }
    condition.notify_one();
    return res;
}

// the destructor joins all threads
inline ThreadPool::~ThreadPool() {
   
    {
   
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers) {
   
        worker.join();
    }
}


/**
 * 引入复杂的并发问题,如数据竞争、死锁等。在上面的示例中,我们假设csvFile对象在不同线程之间的访问是安全的,因为每个线程只是向文件写入数据,并没有进行读取操作。然而,如果有多个线程尝试同时写入同一文件或同一内存位置,就可能出现数据混乱或损坏。

为了确保线程安全,我们需要对共享资源(如文件流、数据集等)进行适当的同步。在C++中,可以使用互斥锁(std::mutex)来保护共享资源。下面是一个修改后的示例,其中使用了互斥锁来确保在写入CSV文件时只有一个线程可以访问:*/

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcmimage.h>

// 互斥锁用于保护对csvFile的访问
std::mutex csvMutex;

// 线程函数,用于提取和写入单个DICOM标签的元数据
void ExtractAndWriteMetadata(std::ofstream& csvFile, DcmDataset* dataset, const DcmTag& tag, const std::string& columnName) {
   
    DcmElement element;
    if (dataset->findAndGetElement(tag, element).good()) {
   
        std::string value;
        switch (element.getTag().getEVR()) {
   
            // ... (省略了处理不同EVR类型的代码)
            default:
                value = "Unknown Type";
                break;
        }

        // 使用互斥锁保护对csvFile的写入
        {
   
            std::lock_guard<std::mutex> lock(csvMutex);
            csvFile << tag.getGroup() << "," << tag.getElement() << "," << columnName << "," << value << std::endl;
        }
    } else {
   
        // 使用互斥锁保护对csvFile的写入
        {
   
            std::lock_guard<std::mutex> lock(csvMutex);
            csvFile << "0000,0000,Error," << "Tag not found" << std::endl;
        }
    }
}

int main() {
   
    // 假设您有一个包含DICOM数据的内存缓冲区
    unsigned char* dicomBuffer = nullptr;
    size_t bufferSize = 0;

    // 加载DICOM数据到DcmFileFormat对象中
    DcmFileFormat fileFormat;
    if (fileFormat.loadFileFromMemory(dicomBuffer, bufferSize) != EC_Normal) {
   
        std::cerr << "Error loading DICOM data from memory." << std::endl;
        return 1;
    }

    // 获取数据集
    DcmDataset* dataset = fileFormat.getDataset();
    if (dataset == nullptr) {
   
        std::cerr << "Error getting DICOM dataset." << std::endl;
        return 1;
    }

    // 打开CSV文件
    std::ofstream csvFile("dicom_metadata.csv");
    if (!csvFile.is_open()) {
   
        std::cerr << "Error opening CSV file." << std::endl;
        return 1;
    }

    // 写入CSV文件标题行
    csvFile << "Tag,Value" << std::endl;

    // 定义要提取的元数据标签
    const std::vector<std::pair<DcmTag, std::string>> tags = {
   
        {
    DcmTag(0x0010, 0x0010), "Patient Name" },
        {
    DcmTag(0x0010, 0x0020), "Patient ID" },
        {
    DcmTag(0x0010, 0x0040), "Patient Sex" },
        {
    DcmTag(0x0010, 0x0030), "Patient Birth Date" },
        {
    DcmTag(0x0008, 0x0060), "Modality" },
        {
    DcmTag(0x0008, 0x0018), "Image Date" },
        {
    DcmTag(0x0008, 0x0019), "Image Time" }
    };

    // 创建并启动线程来提取和写入元数据
    std::vector<std::thread> threads;
    for (const auto& tagPair : tags) {
   
        threads.emplace_back(ExtractAndWriteMetadata, std::ref(csvFile), dataset, tagPair.first, tagPair.second);
    }

    // 等待所有线程完成
    for (auto& thread : threads) {
   
        thread.join();
    }

    // 关闭CSV文件
    csvFile.close();

    return 0;
}


/**
 * 
 * 使用线程池或异步任务可以进一步提高程序的性能和可扩展性,特别是在处理大量并发任务时。下面我将演示如何使用C++的std::async来异步地提取和写入DICOM元数据。

std::async允许你以异步的方式执行一个函数,并返回一个std::future对象,该对象表示异步操作的最终结果。你可以使用std::async来并行处理多个DICOM标签,而无需显式地管理线程。

首先,我们定义一个异步任务函数,它将负责提取并写入单个DICOM标签的元数据:


 * 在这个示例中,我们使用std::vector<std::future<void>>来存储每个异步任务的结果。对于每个DICOM标签,我们调用ExtractAndWriteMetadataAsync函数,该函数返回一个std::future<void>对象,表示异步任务的结果。我们将这些std::future对象存储在futures向量中,以便稍后等待所有任务完成。

使用std::async时,C++运行时会自动管理线程的创建和销毁。你可以通过std::launch::async标志来指示std::async应该异步地执行函数,而不是同步地(即在调用std::async的线程中执行)。这样,std::async就可以根据系统的资源情况来优化线程的使用。

请注意,std::async并不总是创建新的线程来执行任务。在某些情况下,它可能会选择在调用std::async的线程中同步地执行任务,这取决于实现和当前的线程策略。要确保任务真正异步执行,你可以使用std::launch::async标志。

使用线程池或std::async等高级并发模型可以简化多线程编程,并帮助你更好地利用多核处理器。然而,你仍然需要注意避免数据竞争和其他并发问题,特别是当多个线程访问共享资源时。在这种情况下,使用互斥锁(如std::mutex)或其他同步原语来保护共享资源仍然是很重要的。
 * 
*/
#include <future> // for std::async and std::future

// 异步任务函数,用于提取和写入单个DICOM标签的元数据
std::future<void> ExtractAndWriteMetadataAsync(std::ofstream& csvFile, DcmDataset* dataset, const DcmTag& tag, const std::string& columnName) {
   
    return std::async(std::launch::async, ExtractAndWriteMetadata, std::ref(csvFile), dataset, tag, columnName);
}

// ... (省略了ExtractAndWriteMetadata函数的定义)


int main() {
   
    // ... (省略了加载DICOM数据和打开CSV文件的代码)

    // 定义要提取的元数据标签
    const std::vector<std::pair<DcmTag, std::string>> tags = {
   
        // ... (省略了标签定义)
    };

    // 使用std::vector<std::future<void>>来存储异步任务的结果
    std::vector<std::future<void>> futures;

    // 为每个标签创建一个异步任务
    for (const auto& tagPair : tags) {
   
        futures.emplace_back(ExtractAndWriteMetadataAsync(csvFile, dataset, tagPair.first, tagPair.second));
    }

    // 等待所有异步任务完成
    for (auto& future : futures) {
   
        future.get(); // 这将阻塞,直到对应的异步任务完成
    }

    // 关闭CSV文件
    csvFile.close();

    return 0;
}




/**要使用线程池并行处理DICOM元数据提取和写入任务,你可以按照以下步骤进行:

创建一个线程池,指定线程数量。通常,线程数量可以设置为与系统的逻辑处理器数量相同,这样可以充分利用多核处理器的性能。

定义一个任务函数,该函数将负责处理单个DICOM标签的元数据提取和写入操作。

对于每个要处理的DICOM标签,将任务函数提交到线程池中以异步方式执行。

等待所有任务完成。你可以通过std::future对象来获取每个任务的结果,并使用get()方法等待任务完成。

下面是一个使用线程池并行处理DICOM元数据提取和写入的示例代码:

在这个示例中,我们定义了一个ExtractAndWriteMetadataTask函数,它接受一个输出文件流、DICOM数据集、DICOM标签和列名作为参数,并负责提取和写入单个标签的元数据。然后,我们创建了一个线程池,并将任务函数作为lambda表达式提交到线程池中。每个任务都是异步执行的,线程池负责管理和调度这些任务。

通过使用线程池,我们可以并行处理多个DICOM标签的元数据提取和写入操作,从而提高程序的性能。线程池会自动管理线程的创建、销毁和调度,减少了程序员的工作量,并且能够更好地利用多核处理器的性能。

请确保在使用线程池时,对共享资源的访问是线程安全的。如果需要在多个线程之间共享数据集或其他资源,请使用适当的同步机制(如互斥锁)来保护这些资源,以避免数据竞争和其他并发问题。
 * 
*/

#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <future>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcmimage.h>

// 任务函数,负责提取和写入单个DICOM标签的元数据
void ExtractAndWriteMetadataTask(std::ofstream& csvFile, DcmDataset* dataset, const DcmTag& tag, const std::string& columnName) {
   
    DcmElement element;
    if (dataset->findAndGetElement(tag, element).good()) {
   
        std::string value;
        switch (element.getTag().getEVR()) {
   
            // 处理不同EVR类型的代码...
            default:
                value = "Unknown Type";
                break;
        }
        // 写入CSV文件
        csvFile << tag.getGroup() << "," << tag.getElement() << "," << columnName << "," << value << std::endl;
    } else {
   
        // 标签未找到,写入错误信息
        csvFile << "0000,0000,Error," << "Tag not found" << std::endl;
    }
}

int main() {
   
    // 加载DICOM数据和打开CSV文件的代码...

    // 创建线程池,线程数量设置为硬件并发数
    size_t numThreads = std::thread::hardware_concurrency();
    ThreadPool pool(numThreads);

    // 定义要提取的元数据标签
    const std::vector<std::pair<DcmTag, std::string>> tags = {
   
        {
    DcmTag(0x0010, 0x0010), "Patient Name" },
        {
    DcmTag(0x0010, 0x0020), "Patient ID" },
        {
    DcmTag(0x0010, 0x0040), "Patient Sex" },
        {
    DcmTag(0x0010, 0x0030), "Patient Birth Date" },
        {
    DcmTag(0x0008, 0x0060), "Modality" },
        {
    DcmTag(0x0008, 0x0018), "Image Date" },
        {
    DcmTag(0x0008, 0x0019), "Image Time" }
    };

    // 存储std::future对象的vector,用于等待任务完成
    std::vector<std::future<void>> futures;

    // 提交任务到线程池
    for (const auto& tagPair : tags) {
   
        futures.push_back(pool.enqueue(
            [&, dataset = dataset, tag = tagPair.first, columnName = tagPair.second]() {
   
                ExtractAndWriteMetadataTask(csvFile, dataset, tag, columnName);
            }
        ));
    }

    // 等待所有任务完成
    for (auto& future : futures) {
   
        future.get(); // 阻塞直到任务完成
    }

    // 关闭CSV文件
    csvFile.close();

    return 0;
}





/**
 * 确实,一个健壮的线程池实现需要考虑更多的细节和边界情况。下面是对ThreadPool类进行改进的一些建议:

任务队列容器选择:

如果任务队列需要频繁地插入和删除操作,std::queue可能不是最优选择,因为它在删除头部元素时可能需要线性时间。考虑使用std::deque或std::list,它们可以在常数时间内删除头部元素。
任务队列容量限制:

线程池应该有一个任务队列的最大容量限制,以防止无限制的任务堆积导致内存耗尽。当队列满时,可以选择拒绝新任务、等待有空闲线程、或增加线程池的大小。
线程池大小调整:

线程池的大小可能需要根据工作负载动态调整。当任务队列长时间为空时,可以减少线程数量以节省资源;当任务队列持续增长时,可以增加线程数量以提高吞吐量。
任务取消和超时:

提供一个机制来取消正在等待执行的任务或设置任务执行超时。
优雅地停止线程池:

当需要停止线程池时,应该给线程一个机会来清理资源并优雅地退出。这可以通过设置一个标志位(如stop),并在每个工作线程中检查这个标志位来实现。同时,应该确保所有任务都已完成或已被取消后,才允许线程退出。
异常处理:

不仅要捕获并处理任务执行中的异常,还要处理线程启动和终止时的异常。
日志和监控:

添加日志记录功能,以便跟踪线程池的状态和任务执行情况。这有助于调试和性能监控。
任务返回值和状态:

如果任务有返回值或需要报告状态,应该提供一种机制来检索这些信息。
线程本地存储(Thread Local Storage, TLS):

考虑使用线程本地存储来存储线程特有的数据,这样可以避免数据竞争并减少锁的使用。
资源限制和隔离:

在多租户环境中,可能需要限制每个线程池的资源使用,比如CPU时间、内存使用量等,并防止一个线程池的任务影响其他线程池的执行。
任务优先级:

根据任务的重要性和紧急性,可以为任务设置优先级,并相应地调整任务队列的管理策略。
死锁预防:

线程池内部应避免产生死锁的情况,比如在使用互斥锁和其他同步机制时要特别注意锁的顺序。
线程安全的数据结构:

确保所有在多个线程间共享的数据结构都是线程安全的,或者使用适当的同步机制来保护它们。

在继续优化ThreadPool类时,我们可以考虑以下几个方面:

线程池启动和关闭:

提供明确的启动和关闭方法来控制线程池的生命周期。
在关闭线程池时,确保所有正在执行的任务都已完成,并且不再接受新任务。
任务队列的线程安全:

确保任务队列在入队和出队操作时是线程安全的。
使用std::queue配合std::mutex来实现线程安全的队列。
任务返回值的处理:

如果任务有返回值,我们应该使用std::future和std::promise来存储和检索这些返回值。
动态调整线程数量:

根据工作负载调整线程池的大小。这可以通过监视任务队列的长度和/或系统的负载来实现。
任务取消:

提供一种机制来取消正在等待执行的任务。这可能需要任务本身支持取消操作。
错误处理和日志记录:

添加详细的日志记录,以便跟踪和调试线程池的行为。
提供错误处理机制,以便在任务执行失败时通知用户。
资源管理:

确保线程池在使用完毕后释放所有资源,包括线程和动态分配的内存。
任务调度策略:

根据需要实现不同的任务调度策略,如优先级调度、先入先出(FIFO)、最短作业优先(SJF)等。
异常传播:

如果任务抛出异常,并且该异常没有在任务内部捕获,线程会终止。线程池应该捕获这些异常,并允许调用者访问它们。
线程池的扩展性:

设计线程池以支持扩展,比如添加更多功能或集成到更大的系统中。

*/

#include <vector>
#include <queue>
#include <thread>
#include <future>
#include <stdexcept>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <functional>
#include <unordered_map>
#include <iostream>

template <typename Task>
class ThreadPool {
   
public:
    using TaskID = std::future<void>::get_id;

    ThreadPool(size_t threads)
        : stop(false), tasks_queue(), workers(threads), active_tasks(0) {
   
        for (size_t i = 0; i < threads; ++i) {
   
            workers.emplace_back([this] {
   
                while (true) {
   
                    std::function<void()> task;
                    TaskID task_id;
                    std::atomic<bool> cancel_token(false);

                    {
   
                        std::unique_lock<std::mutex> lock(mutex);
                        condition.wait(lock, [this] {
    return stop || (tasks_queue.empty() && active_tasks == 0); });
                        if (stop && tasks_queue.empty()) {
   
                            return;
                        }
                        task = std::move(tasks_queue.front().first);
                        task_id = tasks_queue.front().second;
                        cancel_token = tasks_queue.front().third;
                        tasks_queue.pop();
                        ++active_tasks;
                    }

                    try {
   
                        if (cancel_token.load()) {
   
                            // Task was cancelled before starting
                            throw std::runtime_error("Task cancelled");
                        }
                        task();
                    } catch (const std::runtime_error& e) {
   
                        if (e.what() != "Task cancelled") {
   
                            // Log or handle other exceptions
                            std::cerr << "Exception in thread pool task: " << e.what() << '\n';
                        }
                    } catch (...) {
   
                        // Log or handle unknown exceptions
                        std::cerr << "Unknown exception in thread pool task\n";
                    }

                    {
   
                        std::lock_guard<std::mutex> lock(mutex);
                        --active_tasks;
                    }
                }
            });
        }
    }

    ~ThreadPool() {
   
        stop();
        for (std::thread &worker : workers) {
   
            if (worker.joinable()) {
   
                worker.join();
            }
        }
    }

    ThreadPool(const ThreadPool&) = delete;
    ThreadPool& operator=(const ThreadPool&) = delete;

    template <typename F, typename... Args>
    auto enqueue(F&& f, Args&&... args)
        -> std::future<void> {
   
        auto task = std::make_shared<std::packaged_task<void()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<void> res = task->get_future();
        TaskID task_id = res.get_id();

        {
   
            std::unique_lock<std::mutex> lock(mutex);
            if (stop) {
   
                throw std::runtime_error("enqueue on stopped ThreadPool");
            }
            tasks_queue.emplace(std::move(task), task_id, std::atomic<bool>(false));
        }
        condition.notify_one();
        return res;
    }

    void start() {
   
        std::unique_lock<std::mutex> lock(mutex);
        if (stop) {
   
            throw std::runtime_error("Cannot start a stopped ThreadPool");
        }
        if (active_tasks == 0) {
   
            // Start the worker threads if not already started
            for (auto& worker : workers) {
   
                if (!worker.joinable()) {
   
                    worker = std::thread([this] {
   
                        while (true) {
   
                            std::function<void()> task;
                            TaskID task_id;
                            std::atomic<bool> cancel_token(false);

                            {
   
                                std::unique_lock<std::mutex> lock(mutex);
                                condition.wait(lock, [this] {
    return stop || !tasks_queue.empty(); });
                                if (stop && tasks_queue.empty()) {
   
                                    return;
                                }
                                task = std::move(tasks_queue.front().first);
                                task_id = tasks_queue.front().second;
                                cancel_token = tasks_queue.front().third;
                                tasks_queue.pop();
                                ++active_tasks;
                            }

                            try {
   
                                if (cancel_token.load()) {
   
                                    // Task was cancelled before starting
                                    throw std::runtime_error("Task cancelled");
                                }
                                task();
                            } catch (const std::runtime_error& e) {
   
                                if (e.what() != "Task cancelled") {
   
                                    // Log or handle other exceptions
                                    std::cerr << "Exception in thread pool task: " << e.what() << '\n';
                                }
                            } catch (...) {
   
                                // Log or handle unknown exceptions
                                std::cerr << "Unknown exception in thread pool task\n";
                            }

                            {
   
                                std::lock_guard<std::mutex> lock(mutex);
                                if (cancel_token && cancel_token.load()) {
   
                                    // Task was cancelled during execution
                                } else {
   
                                    --active_tasks;
                                }
                            }
                        }
                    });
                }
            }
        }
    }

    void stop() {
   
        {
   
            std::unique_lock<std::mutex> lock(mutex);
            stop = true; // Set the stop flag
        }
        // Notify all waiting threads that they should exit
        condition.notify_all();
        // Wait for all threads to finish
        for (std::thread &worker : workers) {
   
            if (worker.joinable()) {
   
                worker.join();
            }
        }
        // Clear the task queue and any remaining task references
        while (!tasks_queue.empty()) {
   
            tasks_queue.pop();
        }
        task_ids.clear();
    }

/**
 *     // 在cancel方法中
    bool cancel(TaskID task_id) {
        std::unique_lock<std::mutex> lock(mutex);
        auto it = task_ids.find(task_id);
        if (it == task_ids.end()) {
            return false; // 任务未找到
        }
        std::shared_ptr<std::packaged_task<void()>> task = it->second;
        task_ids.erase(it); // 从task_ids中移除任务
        return true;
    }
*/

    // Cancel a task given its ID
    bool cancel(TaskID task_id) {
   
        std::unique_lock<std::mutex> lock(mutex);
        for (auto it = tasks_queue.begin(); it != tasks_queue.end(); ) {
   
            if (it->second == task_id) {
   
                it->third.store(true); // Set the cancel token
                it = tasks_queue.erase(it); // Erase the task from the queue
                return true; // Task cancelled or will be cancelled if not yet started
            }
            ++it;
        }
        return false; // Task not found
    }

    bool isStopped() const {
   
        return stop;
    }

    size_t getActiveTasks() const {
   
        return active_tasks;
    }

private:
    std::unordered_map<TaskID, std::shared_ptr<std::packaged_task<void()>>> task_ids;
    std::atomic<bool> stop;
    //std::queue<std::function<void()>> tasks_queue;
    std::queue<std::tuple<std::shared_ptr<std::packaged_task<void()>>, TaskID, std::atomic<bool>>> tasks_queue; // 任务队列
    std::vector<std::thread> workers;
    std::mutex mutex;
    std::condition_variable condition;
    size_t active_tasks;
};

三 参考链接

https://www.msn.com/zh-cn/news/other/baidu-comate-2-0%E5%8F%91%E5%B8%83-%E9%9D%A2%E5%90%91%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91%E8%80%85%E5%85%A8%E9%9D%A2%E5%85%8D%E8%B4%B9/ar-BB1la7E5?ocid=msedgdhp&pc=U531&cvid=66123b350f134dccacb9061ef41f5415&ei=51

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
23天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
101 38
|
11天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
7天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
11天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
18天前
|
安全 程序员 API
|
11天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
39 1
|
15天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
16天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
42 4
|
16天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
44 3
|
17天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
29 1

热门文章

最新文章