首页 > 解决方案 > 在处理加载程序线程和大量数据时扩展关键部分的放置位置

问题描述

我已经阅读了有关该主题的教程并观看了有关它的 YouTube 视频,我想我理解为什么使用它们的原因。使用它们的原因有很多,但其中之一是因为现代多核 CPU 具有内部缓存以提高性能(L1 和 L2)。如果一个核心在与该核心关联的缓存中存储了旧内存,这可能会导致一个核心读取旧信息。添加临界区会强制刷新这些缓存。

我试图增加我对这个关键部分必须放在哪里的理解,我觉得大多数在线信息源实际上都无法很好地解释。这就是为什么我在这里问你们专业人士!:)

给你一个简短的例子:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>

static constexpr auto OneMegabyte = 1 * 1024 * 1024;

struct Result
{
    char data[OneMegabyte];
};

std::mutex mutex;
std::list<Result*> results;

void write_lots_of_data(Result* result)
{
    auto f = fopen("large_file.txt", "rb");
    fread(result->data, OneMegabyte, 1, f);
    fclose(f);
}

void read_lots_of_data(Result* result)
{
    //
}

void thread()
{
    auto result = new Result();
    // Writes one megabyte of data from somewhere into Memory::values
    write_lots_of_data(result);
    std::lock_guard<std::mutex> l(mutex);
    results.push_back(result);
}

int main(int argc, char** argv)
{
    std::thread t(&thread);
    while (true)
    {
        std::lock_guard<std::mutex> l(mutex);
        if (results.empty())
            continue;

        auto first_result = results.begin();
        read_lots_of_data(*first_result);
        results.erase(first_result);
        break;
    }
    return 0;
}

访问results受到保护,不会同时被读取和写入 - 我明白这一点。但是放入results列表的实际内存呢?为了安全起见,我是否必须将关键部分放在write_lots_of_data方法之前,还是足以安全地保护results列表?

标签: c++multithreadingthread-safety

解决方案


你的代码是正确的。

互斥锁保护列表,而不是列表节点中包含的数据。因此,正如您所写,将数据填充到 Result 结构中是不受保护的,不需要保护。

阅读时,只有列表需要保护。结果的读数不需要保护。您的代码应该按照编写的方式工作。它可以通过一种方式进行改进:

Result* get_result()
{
    std::lock_guard<std::mutex> l(mutex);
    if (not results.empty())
    {
        Result* first_result = results.front();
        results.pop_front();
        return first_result;
    }

    return nullptr;
}

int main(int argc, char** argv)
{
    std::thread t(&thread);
    while (true)
    {
        Result* first_result = get_result();
        if (first_result)
        {
            read_lots_of_data(first_result);
            delete first_result;
        }
    }

    return 0;
}

将锁限制为仅在列表上操作将有助于提高性能和可读性。读者会意识到只有列表被锁定,而不是结果的读取操作。

在你走得太远之前,请考虑std::unique_ptr<Result>在你的代码中使用 as well 而不是 new/delete。


推荐阅读