[编辑:(从评论中复制)事实证明,问题出在其他地方,但谢谢大家的意见。]
我有一个共享容器类,它使用一个互斥锁来锁定push()和pop()函数,因为我不想同时修改head和tail。这是代码:
int Queue::push( WorkUnit unit )
{
pthread_mutex_lock( &_writeMutex );
int errorCode = 0;
try
{
_queue.push_back( unit );
}
catch( std::bad_alloc )
{
errorCode = 1;
}
pthread_mutex_unlock( &_writeMutex );
return errorCode;
}
当我在调试模式下运行时,一切都很好。当我在发布模式下运行时,我大致在驱动程序开始推送和“同时”弹出时崩溃。如果try / catch块捕获到std :: bad_alloc异常,它会立即强制退出吗?如果是这样,我应该将函数的其余部分包含在finally块中吗?
此外,较慢的调试模式是否有可能成功,因为我的push()和pop()调用从未实际发生过?
答案 0 :(得分:8)
在C ++中,我们使用Resource Acquisition Is Initialization (RAII)来防范异常。
答案 1 :(得分:5)
在异常之后这真的是爆炸吗?你的代码片段更有可能是你的同步不好。首先是互斥锁的名称:“writeMutex”。如果还有“readMutex”,这将不起作用。所有读取,查看和写入操作都需要由相同的互斥锁锁定。
答案 2 :(得分:2)
try / catch是否立即阻止 如果它抓住一个强制退出 std :: bad_alloc异常?
没有。如果在try {...}
块中抛出了std :: bad_alloc,则catch {...}
块中的代码将被触发。
如果您的程序实际崩溃,那么看起来您的push_back调用正在抛出bad_alloc
以外的某些异常(代码中未处理),或者bad_alloc
被抛出在try {...}
区块之外的某个地方。
顺便问一下,你确定你真的想在这里使用try ... catch块吗?
答案 3 :(得分:1)
加
流行音乐是什么样的
创建一个锁定包装类,当它超出范围时会自动释放锁定(如在RAII注释中)
c ++最终没有(感谢stoustrop先生是stroppy)
我会抓住std :: exception或者根本没有(鸭子因为火焰战而低头)。如果你没有捕获,那么你需要包装器
答案 4 :(得分:1)
关于发布/调试:是的,您经常会在两种类型的构建之间发现竞争条件的变化。处理同步时,您的线程将以不同级别的培训运行。编写良好的线程将主要并发运行,而线程编写不良的线程将以高度同步的方式相对于彼此。所有类型的同步都会产生一些级别同步行为。它好像同步和同步来自相同的根词......
所以是的,鉴于调试和发布之间的运行时性能略有不同,线程同步的那些点有时会导致错误的代码在一种类型的构建中显示而不是另一种构建。
答案 5 :(得分:1)
您需要使用RAII
这基本上意味着使用构造函数/析构函数来锁定/解锁资源
这保证了即使异常存在,互斥锁也将始终处于解锁状态。
您应该只使用一个互斥锁来访问列表 即使您只有只读取的线程使用的只读互斥锁。这并不意味着当另一个线程正在更新队列时读取是安全的。队列可能处于某种中间状态,这是由调用push()的线程引起的,而另一个线程正在尝试导航一个invlide中间状态。
class Locker
{
public:
Locker(pthread_mutex_t &lock)
:m_mutex(lock)
{
pthread_mutex_lock(&m_mutex);
}
~Locker()
{
pthread_mutex_unlock(&m_mutex);
}
private:
pthread_mutex_t& m_mutex;
};
int Queue::push( WorkUnit unit )
{
// building the object lock calls the constructor thus locking the mutex.
Locker lock(_writeMutex);
int errorCode = 0;
try
{
_queue.push_back( unit );
}
catch( std::bad_alloc ) // Other exceptions may happen here.
{ // You catch one that you handle locally via error codes.
errorCode = 1; // That is fine. But there are other exceptions to think about.
}
return errorCode;
} // lock destructor called here. Thus unlocking the mutex.
PS。我讨厌使用领先的下划线。
虽然从技术上来说这是可以的(假设成员变量)它很容易搞砸,我宁愿不将''预先添加到idnetifiers。有关标识符名称中“”的完整规则列表,请参阅What are the rules about using an underscore in a C++ identifier?。
答案 6 :(得分:1)
以前使用Locker类的代码示例存在一个主要问题: 当pthread_mutex_lock()失败时你会怎么做? 答案是你必须在构造函数中抛出一个异常,并且它可以被捕获。 精细。 然而, 根据c ++异常规范,从析构函数中抛出异常是一个禁忌。 您如何处理pthread_mutex_unlock FAILURES?
答案 7 :(得分:0)
在任何仪器软件下运行代码都没有任何意义。 你必须使用正确的代码,而不是在valgrind下运行它。
在C中,它的效果非常好:
pthread_cleanup_pop( 0 );
r = pthread_mutex_unlock( &mutex );
if ( r != 0 )
{
/* Explicit error handling at point of occurance goes here. */
}
但是因为c ++是一种软件堕胎,所以没有合理的方法来处理任何程度的确定性的线程编码失败。将pthread_mutex_t包装成一个添加某种状态变量的类的脑死亡的想法只是 - 脑死亡。以下代码不起作用:
Locker::~Locker()
{
if ( pthread_mutex_unlock( &mutex ) != 0 )
{
failed = true; // Nonsense.
}
}
原因在于,在pthread_mutex_unlock()返回此线程之后,可能会从cpu中删除 - 抢占。这意味着.failed公共变量仍然是false。查看它的其他线程将得到错误的信息 - 状态变量表示没有失败,同时pthread_mutex_unlock()失败。即使通过一些运气,这两个语句一次运行,这个线程可能会在~Locker()返回之前被抢占,而其他线程可能会修改.failed的值。底线这些方案不起作用 - 对于应用程序定义的变量没有原子测试和设置机制。
有人说,析构函数永远不应该有失败的代码。其他任何东西都是糟糕的设计。好的。我很想知道在c ++中100%异常和线程安全是一个好设计。