c++,如何创建线程受限/受保护的变量和函数?

Posted

技术标签:

【中文标题】c++,如何创建线程受限/受保护的变量和函数?【英文标题】:c++, how do I create thread-restricted/protected variables and functions? 【发布时间】:2019-06-19 06:00:30 【问题描述】:

我正在构建的应用程序中有三个线程,所有这些线程在应用程序的生命周期内都保持打开状态。几个变量和函数只能从特定线程访问。在我的调试编译中,如果从非法线程访问这些函数或变量之一,我希望运行检查并引发错误,但我不希望这成为我最终编译的开销。我真的只是想要这个,这样我的程序员就不会犯愚蠢的错误,而不是保护我正在执行的程序不犯错误。

最初,我有一个“线程保护”类模板,它可以包装函数的返回类型,并在隐式转换为预期的返回类型之前对构造进行检查,但这似乎不适用于 void 返回类型没有禁用重要警告,也没有解决我的受保护变量问题。

有没有一种方法可以做到这一点,还是超出了语言的范围? '如果你需要这个解决方案,你做错了' cmets 不被赞赏,我设法用这种方法将我的程序的执行时间减少了近一半,但我很可能会犯一个导致无声竞赛的错误条件和最终未定义的行为。

【问题讨论】:

static_assert 对我来说似乎是解决方案。您可以提供线程 ID,并在访问通过 static_assert 检查的变量时。编译后不应该是开销 @Narase 你能给出一些示例代码吗?缺少 setter/getter 我不确定实际放置 static_assert,但我同意它至少会在其中发挥一些作用。 你能提供一个你正在做什么的例子吗?这使得回答更容易。 "如果从非法线程访问这些函数或变量之一,我希望运行检查并抛出错误," - 为什么不只是使其他线程无法访问变量?无需检查,无需开销。将线程对象包装在一个类中,并将私有成员添加到该类中。示例:***.com/questions/56588075/… @TechNeko 在它们周围放置一个包装器,例如“template ProtectedVariable”,其中的值通过“T& get(int id)”。它通过 static_assert 检查 id,当没有抛出任何期望时,它返回引用。由于 static_assert 仅在编译时评估,我很确定优化器会在编译后丢弃整个东西 【参考方案1】:

您所描述的正是assert 宏的用途。

assert(condition)

在调试版本中检查condition。如果为假,程序将在该行抛出异常。在发布版本中,assert 和括号内的任何内容都不会被编译。

如果您解释了您要保护的变量,那将更有帮助。它们是什么类型的?他们来自哪里?他们的寿命是多少?它们是全球性的吗?如果返回的类型为 void,为什么需要保护它?您是如何最终陷入一个线程可能意外访问某些内容的情况的。我不得不猜测,但我会在这里抛出一些想法:

#include <thread>
#include <cassert>


void protectedFunction()

    assert(std::this_thread::get_id() == g_thread1.get_id());


// protect a global singleton (full program lifetime)
std::string& protectedGlobalString()

    static std::string inst;
    assert(std::this_thread::get_id() == g_thread1.get_id());
    return inst;


// protect a class member
int SomeClass::protectedInt()

    assert(std::this_thread::get_id() == g_thread1.get_id());
    return this->m_theVar;


// thread protected wrapper
template <typename T>
class ThreadProtected

    std::thread::id m_expected;
    T               m_val;
public:
    ThreadProtected(T init, std::thread::id expected)
        : m_val(init), m_expected(expected)
     

    T Get()
    
        assert(std::this_thread::get_id() == m_expected);
        return m_val;
    
;

// specialization for void
template <>
class ThreadProtected<void>

public:
    ThreadProtected(std::thread::id expected)
    
        assert(std::this_thread::get_id() == expected);
    
;

assert 是老派。我们实际上被告知停止在工作中使用它,因为它会导致资源泄漏(异常在堆栈中被捕获)。它有可能导致调试头痛,因为调试行为与发布行为不同。很多时候,如果断言的条件是错误的,那么就没有一个好的选择。您通常不想继续运行该函数,但您也不知道要返回什么值。 assert 在开发代码时仍然非常有用。我个人一直使用assert

static_assert 在这里没有帮助,因为您正在检查的条件(例如“哪个线程正在运行此代码?”)是运行时条件。

另一个说明:

不要将要编译的内容放在assert 中。现在看起来很明显,但是很容易做一些像

这样愚蠢的事情
int* p;
assert(p = new(nothrow) int);  // check that `new` returns a value -- BAD!!

检查new 的分配情况很好,但分配不会发生在发布版本中,甚至在开始发布测试之前您都不会注意到!

int* p;
p = new(nothrow) int;
assert(p);  // check that `new` returns a value -- BETTER...

最后,如果您在类主体或 .h 中编写受保护的访问器函数,您可以促使编译器内联它们。


更新以解决问题:

真正的问题是我应该在哪里放置断言宏?是一个 要求我为所有线程编写 setter 和 getter 受保护的变量然后将它们声明为内联并希望它们得到 在最终版本中进行了优化?

您说在访问时应该检查一些变量(仅在调试版本中),以确保正确的线程正在访问它们。因此,理论上,您需要在 每个 此类访问之前都有一个断言宏。如果只有几个地方,这很容易(如果是这种情况,您可以忽略我要说的所有内容)。但是,如果有太多地方开始违反 DRY 原则,我建议编写 getter/setter 并将 assert 放在里面(这是我随便给出的上面的例子)。但是,虽然断言在发布模式下不会增加开销(因为它是有条件编译的),但使用额外的函数(可能)会增加函数调用开销。但是,如果您将它们写在 .h 中,它们很有可能会被内联。

您对我的要求是想出一种方法来做到这一点而无需发布开销。既然我已经提到了内联,我有义务说编译器最了解。通常有编译器特定的方法来强制内联(因为允许编译器忽略 inline 关键字)。您应该在尝试内联之前分析代码。请参阅此问题的答案。 Is it good practice to make getters and setters inline?。您可以通过查看程序集轻松查看编译器是否内联函数。别担心,你不必擅长组装。只需找到调用函数并为 getter/setter 查找 call。如果函数是内联的,您将不会看到 call,而可能会看到 mov

【讨论】:

我很欣赏这些信息,当然很有帮助。但真正的问题是我在哪里放置断言宏?是否要求我为所有受线程保护的变量编写 setter 和 getter,然后将它们声明为内联并希望它们在最终版本中得到优化? 我已经更新了答案。希望它能回答这些问题

以上是关于c++,如何创建线程受限/受保护的变量和函数?的主要内容,如果未能解决你的问题,请参考以下文章

使用受保护构造函数和复制构造函数创建 C++ 非堆工厂对象

未继承的 C++ 受保护变量

如何在 swift 中声明一个“受保护”变量

如何在 C++ 中对受保护的方法进行单元测试?

Java:当具有受保护的构造函数时,如何从 java.io 为 Reader 类创建新的类对象

创建类的受保护构造函数有啥好处和坏处