C++ 用于有序非重入调用的简单断言?
我有两个职能: 按顺序调用的void prepare()和void finish(),如下所示:C++ 用于有序非重入调用的简单断言?,c++,assert,correctness,C++,Assert,Correctness,我有两个职能: 按顺序调用的void prepare()和void finish(),如下所示: prepare(); <do something>; finish(); ... prepare(); <do something>; finish(); 这里有一个竞争条件:prepare的两个并发实例可能同时获取test的值,然后两者在寄存器中递增该值以获得1,然后进行比较以获得true 将其设置为易变的。相反,您应该在test上设置一个互斥锁,如下所示: bo
prepare();
<do something>;
finish();
...
prepare();
<do something>;
finish();
这里有一个竞争条件:
prepare
的两个并发实例可能同时获取test
的值,然后两者在寄存器中递增该值以获得1
,然后进行比较以获得true
将其设置为易变的。相反,您应该在test
上设置一个互斥锁,如下所示:
boost::mutex mtx;
int test = 0;
void prepare()
{
boost::mutex::scoped_try_lock lock(&mtx);
assert(lock.owns_lock());
assert(test++ == 0);
// ...
}
void finish()
{
boost::mutex::scoped_try_lock lock(&mtx);
assert(lock.owns_lock());
assert(--test == 0);
}
代码正常,除非需要允许嵌套
prepare
和finish
调用
如果不允许嵌套,可以使用bool
而不是int
:
bool locked = false;;
void prepare() {
assert( ! locked );
locked = true;
...
}
void finish() {
assert( locked );
locked = false;
...
}
你可能想要改变
int test = 0;
到
为了满足“生产中应省略与此测试相关的任何代码”的要求,您可能需要:
int test = 0;
void prepare() {
// enter critical section
assert(test++ == 0);
.
.
.
// leave critical section
}
void finish() {
// enter critical section
assert(--test == 0);
.
.
.
// leave critical section
}
<>既然你使用C++,为什么不使用RAII?您仍然需要检查重新进入者的使用情况,但RAII大大简化了事情。结合和: 在这个示例中,若并没有事先准备,就无法完成某些事情,即使抛出异常,也总是会完成 关于NDEBUG的旁注:如果您这样使用它,请确保它在所有翻译单元中始终定义或始终未定义,而不是用于assert(允许它在不同点定义和未定义)。如果您使用
进入类别
您可以完全减少检查的需要:
只需让构造函数调用prepare
和析构函数调用finish
。然后会自动强制执行适当地调用它们
请注意,并发性和嵌套问题仍然存在:如果要防止嵌套,则仍然需要某种全局状态(静态类成员?)来跟踪它,并且如果在多个线程中使用它,则对该计数器的访问需要受到互斥保护
还请注意,您还可以将private
operator设置为new/delete
,以防止有人在堆上创建一个而不是销毁它。是否应将volatile用于int测试以缓解任何类型的缓存?是否没有关于int测试被限定为volatile的注释?现在的情况是,测试不能缓存在寄存器中吗?@Fred Nurk你能详细说明一下吗?这个特定的过程不涉及新对象的实例化,它只是应用程序中发生的一个过程。@chriskirk:移动到一个答案。Re:volatile
--请阅读此内容,但忽略(或至少带着一块盐)已接受的答案,这意味着volatile
对于多线程编程非常有用。但事实并非如此。嗯,我认为最好是将所有内容都放在一个断言中,以消除使用#if NDEBUG的需要。你觉得怎么样?@chriskirk:你仍然可以这样做:assert((locked=!locked)=true)代码>在准备和断言((锁定=!锁定==错误);`在finish
中。无论如何,我更喜欢使用#如果NDEBUG
,我认为这样理解代码更容易,但这只是一个品味问题。我认为在大多数情况下,使用bool
比使用int
成本更低,而且在我看来,代码逻辑更清晰。使用test
astatic int
可能足以使其优化。@larsmans:可能吧。有些编译器可能会抱怨它从来没有被使用过。这是一个传统的C++引用计数习惯用法。但它不能解决并发问题。您在哪个平台上?执行此练习只是为了检查prepare()和finish()是否被正确调用。它是64位Linux上使用gcc 4.1.2的单线程应用程序。@chris,如果它是单线程的,那么您不需要线程同步。请编辑您的问题以删除对并发的引用,并删除并发和重入标记。@chriskirk:否,原因如下:嗯。。。我不认为这个程序是多线程的。是吗?@peoro:OP问到并发访问。是的,有一个竞争条件。这是什么平台?这个应用程序不应该是并发的。该例程不支持并发或重入。整个帖子都在问,在开发/测试模式下(而不是在生产模式下)断言()的最佳方式是什么。就像开发过程中的基本检查一样。对不起,我忘了提到这个应用程序对延迟非常敏感,因此RAII不合适。谢谢你的建议,在典型的应用场景和需求下,这是很好的。@chriskirk:你认为上面的代码会对延迟产生怎样的负面影响?与非RAII情况相比,您看到了什么开销?私有操作符new/delete不会阻止某人在堆上创建一个。有时你不得不接受人们可能会破坏你的代码,如果他们尝试的话。
#ifndef NDEBUG
int test = 0;
#endif
int test = 0;
void prepare() {
// enter critical section
assert(test++ == 0);
.
.
.
// leave critical section
}
void finish() {
// enter critical section
assert(--test == 0);
.
.
.
// leave critical section
}
struct Frobber {
Frobber() {
assert(mtx.try_lock());
#ifndef NDEBUG
try { // in case prepare throws
#endif
prepare();
#ifndef NDEBUG
}
catch (...) {
mtx.unlock();
throw;
}
#endif
}
void something();
// And the other actions that can be performed between preparation and finishing.
~Frobber() {
finish();
#ifndef NDEBUG
mtx.unlock();
#endif
}
private:
#ifndef NDEBUG
static boost::mutex mtx;
#endif
Frobber(Frobber const&); // not defined; 0x: = delete
Frobber& operator=(Frobber const&); // not defined; 0x: = delete
};
#ifndef NDEBUG
boost::mutex Frobber::mtx;
#endif
void example() {
Frobber blah; // instead of prepare()
blah.something();
// implicit finish()
}