C++ 用于有序非重入调用的简单断言?

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

我有两个职能:

按顺序调用的void prepare()和void finish(),如下所示:

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
a
static 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()
}