Почему этот код не работает в valgrind (helgrind)? - PullRequest
2 голосов
/ 26 августа 2010

** РЕШЕНО: Внутри конструктора моего класса у меня были гонки по созданию семафора с конструкцией Thread, где я хотел, чтобы сначала создавался семафор, а затем Thread. Решение, которое сработало для меня, состояло в том, чтобы сначала создать семафор в базовом классе, чтобы я мог зависеть от него в своем производном классе. **

У меня довольно небольшая программа на C ++ pthreads, которая отлично работает в нормальных условиях. Тем не менее, при использовании в программе инструментов проверки ошибок потока Valgrind он, похоже, обнаруживает состояние гонки. Что делает это состояние гонки особенно трудным для избежания, так это то, что оно происходит внутри класса "Semaphore" (который на самом деле просто инкапсулирует 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");
}
  • Обратите внимание, как конструктор Семафора создает новый sem_t с именем "m_Sem" и создает исключительную ситуацию в крайне маловероятном сценарии, когда m_Sem по-прежнему равен 0. Это просто означает, что для этого конструктора должно быть невозможным, чтобы m_Sem был равен 0. Ну ... перейти к Semaphore :: lock: независимо от того, из какого потока вызывается эта функция (а также конструктор), теоретически - по-прежнему невозможно, чтобы m_Sem был равен 0, верно? Что ж, когда я запускаю свою программу под helgrind, Semaphore :: lock, конечно же, заканчивает тем, что выдает это исключение «Semaphore :: lock error: m_Sem == 0», которое я действительно считал невозможным.

Я использовал этот класс Semaphore в других программах, которые без проблем передают helgrind, и я действительно не уверен, что я делаю здесь что-то особенное, что вызывает проблему. Согласно helgrind, гонка происходит между записью в конструкторе Семафора в одном потоке и чтением в 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 голосов
/ 27 августа 2010

Хотя похоже, что поток пытается использовать семафор в потоке 2, прежде чем поток 1 завершит выполнение конструктора.В этом случае возможно, что m_Sem будет NULL (0) или любым другим значением.

0 голосов
/ 31 августа 2010

Хорошо, я нашел проблему. Мой класс ActionQueue создавал (в дополнение к другим) два объекта при создании: семафор и поток. Проблема была в том, что этот поток использовал этот семафор. Я неправильно предположил, что Семафор будет создан автоматически перед входом в конструктор, так как он является объектом-членом. Моим решением было извлечь ActionQueue из базового класса, в котором построен мой семафор; таким образом, к тому времени, когда я доберусь до конструктора ActionQueue, я могу рассчитывать на уже создаваемые члены базового класса.

...