首页 文章

为什么这个代码在valgrind(helgrind)下失败了?

提问于
浏览
2

**已解决:在我 class 的构造函数中,我有一个Semaphore的构造赛车,其中有一个Thread的构造,我希望首先创建Semaphore,然后创建Thread . 对我有用的解决方案是首先在基类中创建信号量,这样我就可以在派生类中依赖它 . **

我有一个相当小的pthreads C程序,在正常情况下工作正常 . 但是,当在程序上使用valgrind的线程错误检查工具时,它似乎可以发现竞争条件 . 是什么让这种竞争条件特别难以避免的是它发生在“信号量”类(实际上只是封装sem_init,sem_wait和sem_post)中,所以我无法用另一个信号量修复它(并且不应该) . 我不认为valgrind会给出误报,因为我的程序在valgrind下运行时会显示不同的行为 .

这是Semaphore.cpp *:

#include "Semaphore.h"
#include <stdexcept>
#include <errno.h>
#include <iostream>

Semaphore::Semaphore(bool pshared,int initial)
   : m_Sem(new sem_t())
{
  if(m_Sem==0)
    throw std::runtime_error("Semaphore constructor error: m_Sem == 0");
  if(sem_init(m_Sem,(pshared?1:0),initial)==-1)
    throw std::runtime_error("sem_init failed");
}

Semaphore::~Semaphore()
{
    sem_destroy(m_Sem);
    delete m_Sem;
}
void Semaphore::lock()
{
  if(m_Sem==0)
    throw std::runtime_error("Semaphore::lock error: m_Sem == 0");

  int rc;
  for(;;){
    rc = sem_wait(m_Sem);
    if(rc==0) break;
    if(errno==EINTR) continue;
    throw std::runtime_error("sem_wait failed");
  }
}
void Semaphore::unlock()
{
  if(sem_post(m_Sem)!=0)
    throw std::runtime_error("sem_post failed");
}
  • 请注意Semaphore的构造函数如何创建一个名为"m_Sem"的新sem_t,并在极不可能的情况下抛出异常,m_Sem仍然等于0.这只是意味着此构造函数不可能允许m_Sem等于0.嗯...移动对Semaphore :: lock:无论从哪个线程调用此函数(以及构造函数),从理论上讲,m_Sem仍然不可能为0,对吧?好吧,当我在helgrind下运行我的程序时,Semaphore :: lock,果然,最终会抛出这个异常"Semaphore::lock error: m_Sem==0",我真的认为应该是不可能的 .

我已经在其他程序中使用了这个Semaphore类,它没有任何问题地传递给helgrind,而且我真的不确定我在这里做什么特别导致问题 . 根据helgrind的说法,竞争发生在一个线程中的Semaphore构造函数的写入和另一个线程中的Semaphore :: lock中的读取之间 . 老实说,我甚至不知道这是怎么可能的:对象的方法如何与该对象的构造函数具有竞争条件? C是否保证在可以调用对象上的方法之前调用构造函数?即使在多线程环境中,如何侵犯它?

无论如何,现在为valgrind输出 . 我正在使用valgind版本“Valgrind-3.6.0.SVN-Debian” . Memcheck说一切都很顺利 . 这是helgrind的结果:

$ valgrind --tool=helgrind --read-var-info=yes ./try
==7776== Helgrind, a thread error detector
==7776== Copyright (C) 2007-2009, and GNU GPL'd, by OpenWorks LLP et al.
==7776== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==7776== Command: ./try
==7776==
terminate called after throwing an instance of '==7776== Thread #1 is the program's root thread
==7776==
==7776== Thread #2 was created
==7776== at 0x425FA38: clone (clone.S:111)
==7776== by 0x40430EA: pthread_create@@GLIBC_2.1 (createthread.c:249)
==7776== by 0x402950C: pthread_create_WRK (hg_intercepts.c:230)
==7776== by 0x40295A0: pthread_create@* (hg_intercepts.c:257)
==7776== by 0x804CD91: Thread::Thread(void* (*)(void*), void*) (Thread.cpp:10)
==7776== by 0x804B2D5: ActionQueue::ActionQueue() (ActionQueue.h:40)
==7776== by 0x80497CA: main (try.cpp:9)
==7776==
==7776== Possible data race during write of size 4 at 0x42ee04c by thread #1
==7776== at 0x804D9C5: Semaphore::Semaphore(bool, int) (Semaphore.cpp:8)
==7776== by 0x804B333: ActionQueue::ActionQueue() (ActionQueue.h:40)
==7776== by 0x80497CA: main (try.cpp:9)
==7776== This conflicts with a previous read of size 4 by thread #2
==7776== at 0x804D75B: Semaphore::lock() (Semaphore.cpp:26)
==7776== by 0x804B3BE: Lock::Lock(Semaphore&) (Lock.h:17)
==7776== by 0x804B497: ActionQueue::ActionQueueLoop() (ActionQueue.h:56)
==7776== by 0x8049ED5: void* CallMemFun, &(ActionQueue::ActionQueueLoop())>(void*) (CallMemFun.h:7)
==7776== by 0x402961F: mythread_wrapper (hg_intercepts.c:202)
==7776== by 0x404296D: start_thread (pthread_create.c:300)
==7776== by 0x425FA4D: clone (clone.S:130)
==7776==
std::runtime_error'
  what(): Semaphore::lock error: m_Sem == 0
==7776==
==7776== For counts of detected and suppressed errors, rerun with: -v
==7776== Use --history-level=approx or =none to gain increased speed, at
==7776== the cost of reduced accuracy of conflicting-access information
==7776== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 5 from 5)

任何有git和valgrind的人都可以通过检查我的git repo分支(对于记录,目前在提交262369c2d25eb17a0147)中的代码来重现这一点,如下所示:

$ git clone git://github.com/notfed/concqueue -b semaphores
$ cd concqueue
$ make 
$ valgrind --tool=helgrind --read-var-info=yes ./try

2 回答

  • 3

    虽然看起来线程在线程1完成运行构造函数之前尝试在线程2中使用信号量 . 在这种情况下,可以使m_Sem为NULL(0)或任何其他值 .

  • 0

    好的,我发现了问题 . 我的ActionQueue类在构造时创建了两个对象(除了其他对象):一个Semaphore和一个Thread . 问题是,这个线程正在使用那个信号量 . 我错误地认为在进入构造函数之前会自动创建信号量,因为它是一个成员对象 . 我的解决方案是从构建我的信号量的基类派生ActionQueue;这样,当我到ActionQueue的构造函数时,我可以依赖已经构建的基类成员 .

相关问题