ошибка режима выпуска, но не в режиме отладки

Мой код отлично работает в режиме отладки, но не работает в режиме выпуска.

Вот фрагмент моего кода, где он не работает:

LOADER->AllocBundle(&m_InitialContent);
while(!m_InitialContent.isReady())
{
    this->LoadingScreen();
}

AllocBundle () загрузит содержимое, содержащееся в m_InitialContent, и установит для него статус готовности true, когда это будет сделано. Это реализовано с использованием многопоточности.

this->LoadingScreen() должен отображать экран загрузки, но на данный момент это еще не реализовано, поэтому функция имеет пустое тело.

По-видимому, это может быть причиной ошибки: если я дам функции LoadingScreen () одну строку кода: std::cout<<"Loading"<<std::endl;, она будет работать нормально.

Если я этого не сделаю, то код застрянет на while(!m_InitialContent.isReady()). Он даже не перейдет к коду между скобками (this->LoadingScreen();). И, по-видимому, он также не обновляет выражение в операторе while, потому что оно остается там навсегда.

Есть ли у кого-нибудь идеи, что может быть причиной этого? И если да, то в чем может быть проблема? Я полностью озадачен.


РЕДАКТИРОВАТЬ: Дополнительный код по запросу

член ContentLoader: details::ContentBundleAllocator m_CBA;

    void ContentLoader::AllocBundle(ContentBundle* pBundle)
    {
        ASSERT(!(m_CBA.isRunning()), "ContentBundleAllocator is still busy");
        m_CBA.Alloc(pBundle, m_SystemInfo.dwNumberOfProcessors);
    }

void details::ContentBundleAllocator::Alloc(ContentBundle* pCB, UINT numThreads)
{
    m_bIsRunning = true;
    m_pCB = pCB;
    pCB->m_bIsReady = false;


    m_NumRunningThrds = numThreads;
    std::pair<UINT,HANDLE> p;
    for (UINT i = 0; i < numThreads; ++i)
    {
        p.second = (HANDLE)_beginthreadex(NULL,
                                          NULL,
                                          &details::ContentBundleAllocator::AllocBundle,
                                          this,
                                          NULL,&p.first);
        SetThreadPriority(p.second,THREAD_PRIORITY_HIGHEST);
        m_Threads.Insert(p);
    }
}

unsigned int __stdcall details::ContentBundleAllocator::AllocBundle(void* param)
{
//PREPARE
    ContentBundleAllocator* pCBA = (ContentBundleAllocator*)param;

//LOAD STUFF [collapsed for visibility+]

   //EXIT===========================================================================================================
        pCBA->m_NumRunningThrds -= 1;
        if (pCBA->m_NumRunningThrds == 0)
        {
            pCBA->m_bIsRunning = false;
            pCBA->m_pCB->m_bIsReady = true;
            pCBA->Clear();
    #ifdef DEBUG
            std::tcout << std::endl;
    #endif
            std::tcout<<_T("exiting allocation...")<<std::endl;
        }

    std::tcout<<_T("exiting thread...")<<std::endl;
    return 0;
}

bool isReady() const {return m_bIsReady;}

person xcrypt    schedule 10.05.2012    source источник
comment
По крайней мере, вам нужно показать код AllocBundle () и isReady ().   -  person tinman    schedule 10.05.2012
comment
@tinman это круто, сек   -  person xcrypt    schedule 10.05.2012
comment
и, конечно, вам нужно сообщить нам (с исходным кодом), что происходит в фоновых потоках   -  person Vlad    schedule 10.05.2012
comment
Если это какая-то ошибка памяти, это может помочь запустить ее через valgrind ... или любой другой коммерческий инструмент Windows, который делает то же самое. Они отлавливают некоторые ошибки, которые появляются только в режиме выпуска даже во время отладки.   -  person Torp    schedule 10.05.2012
comment
В вашем коде есть несколько директив препроцессора. Проверьте, правильно ли инициализированы эти части кода в режиме выпуска.   -  person Andre Fly    schedule 10.05.2012
comment
xcrypt: это много кода. Не могли бы вы сделать нам одолжение и удалить все детали, кроме необходимых для устранения проблемы?   -  person Vlad    schedule 10.05.2012
comment
@Vlad На самом деле я не показываю намного больше кода, который также важен для решения проблемы. Вот почему я не показывал это изначально, я думал, что самая важная часть находится в небольшом фрагменте выше.   -  person xcrypt    schedule 10.05.2012
comment
@xcrypt: я боюсь, что весь код, связанный с текстурами и т. д., возможно, не имеет отношения к вопросу. Единственные важные части - это те, которые запускают / завершают потоки и информируют другой код о завершении.   -  person Vlad    schedule 10.05.2012
comment
Я не уверен, правильно ли это или как лучше всего объяснить это в ответе, но ваше чтение m_bIsReady, вероятно, не читается из памяти из-за оптимизации компилятора. Вероятно, вам следует использовать для этого объект синхронизации ОС, такой как событие ручного сброса или condvar, а не POD и (надеясь), что потоки не оптимизируют доступ и не выполняют полные чтения и записи через кеши ЦП в память.   -  person tinman    schedule 10.05.2012
comment
@Vlad как хотите, я удалил.   -  person xcrypt    schedule 10.05.2012
comment
@xcrypt: спасибо, теперь понятнее.   -  person Vlad    schedule 10.05.2012
comment
@tinman не могли бы вы подробнее рассказать об этом? Я не уверен, что вы на самом деле говорите.   -  person xcrypt    schedule 10.05.2012
comment
@xcrypt: Я пытался сказать то, что Влад сказал в своем ответе.   -  person tinman    schedule 10.05.2012


Ответы (3)


Когда вы компилируете свой код в режиме отладки, компилятор делает множество вещей за кулисами, что предотвращает многие ошибки, допущенные программистом, от сбоя приложения. Когда вы запускаете Release, все ставки отключены. Если ваш код неверен, у вас гораздо больше шансов получить сбой в Release, чем в Debug.

Несколько вещей, которые нужно проверить:

  1. Убедитесь, что все переменные правильно инициализированы
  2. Убедитесь, что у вас нет тупиковых ситуаций или условий гонки
  3. Убедитесь, что вы не передаете указатели на локальные объекты, которые были освобождены.
  4. Убедитесь, что ваши строки правильно завершены NULL
  5. Не catch исключений, которых вы не ожидаете, а затем продолжайте работу, как будто ничего не произошло.
person John Dibling    schedule 10.05.2012
comment
Меня это всегда беспокоило. ИМХО, у нас не должно быть никаких защитных устройств при отладке, чтобы мы сбоили как можно чаще и имели защитные механизмы в выпуске, чтобы, если что-то все же проскользнет, ​​мы не рухнем. Но, я знаю, у всего есть свое вычислительное время, и даже затраты на меры безопасности ... - person RedX; 10.05.2012
comment
RedX: Мне нравится разрабатывать в Debug, потому что меры предосторожности иногда бывают полезны. Но вы можете развиваться и в выпуске. Просто отключите оптимизацию, и отладчик будет более или менее работать должным образом. - person John Dibling; 10.05.2012

Вы получаете доступ к переменной m_bIsReady из разных потоков без ограничений памяти. Это неверно, так как это может быть кэшировано оптимизатором или кешем процессора. Вы должны защитить эту переменную от одновременного доступа с помощью CriticalSection, мьютекса или любого другого примитива синхронизации, доступного в вашей библиотеке.

Обратите внимание, что могут быть и другие ошибки, но эта тоже определенно ошибка. Практическое правило: каждая переменная, к которой осуществляется доступ из разных потоков, должна быть защищена мьютексом / критической секцией / чем угодно.

person Vlad    schedule 10.05.2012
comment
Я не понимаю, как это может вызвать проблему? Он не охраняется, нет, но всегда будет достигать 0, и часть m_bIsReady==0 сработает так, как задумано? - person xcrypt; 10.05.2012
comment
@xcrypt: нет, оптимизатор может кэшировать значение в основном потоке. - person Vlad; 10.05.2012
comment
@xcrypt: прочтите эту статью о всей проблеме и важности блокировки: aristeia.com/Papers/ DDJ_Jul_Aug_2004_revised.pdf (интересное начинается с 5 страницы) - person Vlad; 10.05.2012
comment
Насколько я понял, мне явно не нужна блокировка, поэтому достаточно указать номер данных volatile? - person xcrypt; 11.05.2012
comment
@xcrypt: Боюсь, нет, volatile недостаточно. По-прежнему нужен какой-то барьер памяти. Обратите внимание на цитату со страницы 10 статьи выше: хотя стандарт запрещает компиляторам переупорядочивать чтение и запись в изменчивые данные внутри потока, он вообще не налагает ограничений на такое переупорядочение между потоками. - person Vlad; 11.05.2012
comment
@xcrypt: я бы предложил попробовать и посмотреть, добавляется ли критический раздел (msdn.microsoft .com / en-us / library / ms885212.aspx) подойдет. (Кажется, вы используете чистый WinAPI.) - person Vlad; 11.05.2012
comment
@xcrypt: извините, неправильная ссылка, вам нужен msdn.microsoft. com / en-us / library / windows / desktop / ms682608.aspx (хотя должно быть почти таким же) - person Vlad; 11.05.2012
comment
извините за беспокойство, но это: stackoverflow.com/questions/4557979/ утверждает, что volatile на самом деле бесполезен для многопоточности в C ++. Во что я должен верить? В статье, которую вы связали, volatile используется для обеспечения безопасности одноэлементных потоков, но эта ссылка говорит мне, что это бесполезно? - person xcrypt; 11.05.2012
comment
@xcrypt из статьи (конец раздела 5): Unfortunately, this all does nothing to address the first problem: C++’s abstract machine is single-threaded, and C++ compilers may choose to generate thread-unsafe code from source like the above, anyway. - person msam; 11.05.2012
comment
@xcrypt: Я поддерживаю идею о том, что volatile бесполезен :( Обе цитаты выше подтверждают это. Хотя volatile может работать для некоторых настроек компилятора и компиляции, в целом это не гарантирует безопасность потоков. - person Vlad; 11.05.2012
comment
@msam о да, мне показали эту часть около 10 раз, но в ней не говорится, что volatile бесполезен. В статье нет ничего подобного, только того, что одного volatile недостаточно для многопроцессорных систем. И спасибо, влад - person xcrypt; 11.05.2012
comment
Volatile также кажется полезным для многопоточного программирования, хотя и не для замены каких-либо барьеров памяти, а для обозначения того, что значение объекта может измениться в любое время. volatile bool m_bIsRunning; while (m_bIsRunning) Если вы не поместите туда volatile, он может быть оптимизирован компилятором - person xcrypt; 11.05.2012
comment
@msam: Могу поспорить, что volatile недостаточно. Рассмотрим случай, когда значение не выгружается из кеша процессора. При наличии нескольких процессоров и отсутствии барьеров для памяти не гарантируется, что представление памяти разными потоками будет согласованным. - person Vlad; 11.05.2012

на первый взгляд кажется, что m_NumRunningThrds не защищен от одновременного доступа, поэтому if (pCBA->m_NumRunningThrds == 0) может никогда не быть удовлетворенным.

person msam    schedule 10.05.2012
comment
Он всегда туда попадает. Я вижу это из-за записи в консоль. std::tcout<<_T("exiting allocation...")<<std::endl; - person xcrypt; 10.05.2012