Soru std :: thread :: join () VS2012 RC kullanırken main () çıkışından sonra çağrılırsa kilitleniyor


Aşağıdaki örnek, Ubuntu 12.04 üzerinde Clang 3.2 veya GCC 4.7 kullanılarak derlendiyse başarılı bir şekilde çalışır (yani askıda kalmaz), ancak VS11 Beta veya VS2012 RC kullanarak derlersek askıda kalıyor.

#include <iostream>
#include <string>
#include <thread>
#include "boost/thread/thread.hpp"

void SleepFor(int ms) {
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

template<typename T>
class ThreadTest {
 public:
  ThreadTest() : thread_([] { SleepFor(10); }) {}
  ~ThreadTest() {
    std::cout << "About to join\t" << id() << '\n';
    thread_.join();
    std::cout << "Joined\t\t" << id() << '\n';
  }
 private:
  std::string id() const { return typeid(decltype(thread_)).name(); }
  T thread_;
};

int main() {
  static ThreadTest<std::thread> std_test;
  static ThreadTest<boost::thread> boost_test;
//  SleepFor(100);
}

Sorun böyle görünüyor std::thread::join() sonra çağrılırsa asla geri dönmez main çıktı. Adresinde engellendi WaitForSingleObject içinde _Thrd_join cthread.c dosyasında tanımlanmıştır.

uncommenting SleepFor(100); sonunda main programın düzgün şekilde çıkmasını sağlar. std_test statik olmayan. kullanma boost::thread ayrıca sorunu önler.

Bu yüzden tanımlanmamış davranışları buraya çağırıp çağırmayacağımı (belki de bana göre değil) veya VS2012'ye karşı bir hata yapmam gerekip gerekmediğini bilmek isterim.


21
2018-06-06 13:30


Menşei


Beta ve Serbest Bırakma Adayları - Jaywalker
@Jaywalker Eh sanırım istediğim bu. Beklemek mantıklı mı? join() sonra çağrılırsa askıda kalmaz main çıktı. Eğer öyleyse, bu MS Connect'e daha sonradan daha çabuk girmek istediğim bir hatadır. - Fraser
Eğer test etmeyi deneyebilir misin std::thread::joinable() hala asılmadan önce gerçek mi dönüyor? - Plexico
Lütfen bir hata yapın Microsoft Connect (Bu olup olmadığını bilmiyorum aslında bir böcek; Ben bu tanıdık değilim std::thread Şartname. Ama yine de raporlamaya değer olurdu.) - James McNellis
@Plexico thread_.joinable() geri dönüyor true. - Fraser


Cevaplar:


Fraser’ın örnek kodunun bağlantı hatasından izlenmesi (https://connect.microsoft.com/VisualStudio/feedback/details/747145) VS2012 RTM ile oldukça basit bir deadlocking vakası gibi görünüyor. Bu muhtemelen belirli değil std::thread - muhtemelen _beginthreadex aynı kaderi çekiyor.

Hata ayıklayıcısında gördüğüm şudur:

Ana iş parçacığında main() işlev tamamlandı, süreç temizleme kodu, denilen kritik bir bölüm aldı _EXIT_LOCK1, yıkıcı denir ThreadTestve ikinci iş parçacığından çıkmak için (süresiz olarak) bekler. join()).

İkinci iş parçacığının anonim işlevi tamamlandı ve elde etmek için bekleyen iş parçacığı temizleme kodunda _EXIT_LOCK1 kritik Bölüm. Ne yazık ki, şeylerin zamanlaması nedeniyle (ki bu ikinci parçanın anonim işlevinin ömrü, main() işlev) ana konu zaten kritik bölümün sahibi.

Kilitlenme.

Ömrünü uzatan herhangi bir şey main() ikinci iplik elde edebilir _EXIT_LOCK1 ana iplikten önce kilitlenme durumunu önler. İşte bu yüzden uyumaktan rahatsızlık duymak main() temiz bir kapatma ile sonuçlanır.

Alternatif olarak, statik anahtar kelimeyi ThreadTest yerel değişken, destructor çağrısı sonuna kadar hareket ettirilir main() Daha sonra ikinci iplik çıkana kadar bloke olan fonksiyon (süreç temizleme kodunun yerine) - kilitlenme durumundan kaçının.

Ya da bir işlev ekleyebilirsin ThreadTest Bu çağırır join() ve sonunda bu işlevi çağırın. main() - yine kilitlenme durumundan kaçınmak.


23
2017-11-22 07:37



Güzel analiz. - Michael Burr
@CWoods - MS Connect hata raporunun bağlantısını cevabınıza ekleyebilir misiniz? Seninkini doğru olarak işaretlemek ve benimkini silmek istiyorum. - Fraser
@Frazer - Bitti! Yardım ettiğim için sevindim! - CWoods
Bu sorun hala var ve C ++ 11 konuları etkiler inanılmaz. 10 yıl önce bir Windows pthreads bağlantı noktasıyla benzer davranışlar gördüm ... - Florian Winter
Evet, hata hala MSVC ++ 2013'te var. _beginthreadex() ben de: C ++ 11'den yeni kayıt yaptırdım thread MSVC ++ için _beginthreadex(), asla. MSDN de bunu söylüyor CreateThread CRT kullanan iplikler için kullanılamaz (ve ihtiyacım var FILE* operasyonlar): msdn.microsoft.com/en-us/library/windows/desktop/... . Statik olmayan bir tekilde iş parçacığı oluştuğundan, statik olmayan değişkene yeniden odaklanma seçeneği de yoktur. Yani MSVC ++ 2013 iş parçacığı üzerinde bozuk ve CUDA Toolkit henüz MSVC ++ 2015'i desteklemiyor. (% - Serge Rogatch


Bunun VS2012 ile ilgili eski bir soru olduğunu anlıyorum, ama hata hala VS2013'te mevcut. VS2013'e takılanlar için, belki de Microsoft'un VS2015 için yükseltme fiyatlandırması yapmayı reddetmesi nedeniyle, aşağıdaki analizi ve çözümü sunuyoruz.

Sorun şu ki muteks (at_thread_exit_mutex) tarafından kullanılan _Cnd_do_broadcast_at_thread_exit() kesin koşullara bağlı olarak henüz başlatılmamış veya halihazırda imha edilmiştir. Eski durumda, _Cnd_do_broadcast_at_thread_exit() kapatma sırasında mutex'i başlatmaya çalışır ve bir çıkmaza neden olur. İkinci durumda, muteksin zaten atexit yığını üzerinden tahrip edildiği yerde, program çıkış yolunda çökecektir.

Bulduğum çözüm açıkça aramak _Cnd_do_broadcast_at_thread_exit() (başlangıçta neşeyle bildirilen) program başlangıcı sırasında. Bu, muteks'in başka herhangi bir kişiye ulaşmaya çalışmasından önce yaratılmasının yanı sıra, muteksin mümkün olan en son ana kadar varlığını sürdürmesini sağlama etkisine de sahiptir.

Bu nedenle, sorunu düzeltmek için, aşağıdaki kodu bir kaynak modülün alt kısmına, örneğin ana () öğesinin altına yerleştirin.

#pragma warning(disable:4073) // initializers put in library initialization area
#pragma init_seg(lib)

#if _MSC_VER < 1900
struct VS2013_threading_fix
{
    VS2013_threading_fix()
    {
        _Cnd_do_broadcast_at_thread_exit();
    }
} threading_fix;
#endif

6
2017-12-10 16:46



Bu geçici çözüm, Visual Studio 2013 Ultimate'da benim için çalışmaz. - leetNightshade
Benim için bir DLL, MSVC ++ 2013 Güncelleştirmesi 5 tek bir etkin nesne için çalışmaz. - Serge Rogatch


İşlerinizin sonlandırıldığına ve ana işlevinizin sona ermesinden ve statik yıkımdan önce kaynaklarının serbest kaldığına inanıyorum. Bu, en az VC6'ya kadar olan VC çalışma zamanlarının davranışıdır.

Ana iş parçacığı sonlandığında alt iş parçacığı çıkış yap

Pencerelerde ipliği ve proses temizleme işlemini hızlandırın


1
2018-06-06 13:38



Bu cevap pencerelere özgüdür, ancak VS'yi kullandığınızdan beri varsaydım. - TractorPulledPork
Windows kullanıyorum ama maalesef cevabın doğru olduğundan emin değilim. Ana iş parçacığı yürüten join, bu yüzden fonksiyon olsa bile main Çıktı, ana iş parçacığı sonlandırılmadı. Yine de teşekkürler. - Fraser
Daha alakalı içerik: stackoverflow.com/questions/8001989/... - TractorPulledPork
Belki de ipleri öldüren kod, ana çağrının çalışma zamanındaki bir yerdedir, ama sizin tahrillerinizin çağrıldığı sırada iş parçacığınızın gitmiş olduğundan emin olabilirim. - TractorPulledPork
Program asılıyken yürütmeyi durdurabilir ve hem ana iş parçacığı hem de join çağrı ve çocuk iş parçacığı _lock mlock.c, dolayısıyla threadlar sonlandırılmadı. Ayrıca, cevabınız sorumu gerçekten cevaplamıyor. Bu, tanımlanmamış davranışların veya VS2012'deki bir hatanın asılması mı? - Fraser


Bu hatayı bir günlüğüne savundum ve şu işe yaradı: en az kirli olan şey şu oldu:

Geri döndürmek yerine, iş parçacığını sonlandırmak için standart Windows API işlev çağrısı ExitThread () kullanılabilir. Bu yöntem elbette std :: thread nesnesinin ve ilişkili kitaplığın iç durumunu bozabilir, ancak program zaten sonlandırılacağından, bu yüzden olsun.

#include <windows.h>

template<typename T>
class ThreadTest {
 public:
  ThreadTest() : thread_([] { SleepFor(10); ExitThread(NULL); }) {}
  ~ThreadTest() {
    std::cout << "About to join\t" << id() << '\n';
    thread_.join();
    std::cout << "Joined\t\t" << id() << '\n';
  }
 private:
  std::string id() const { return typeid(decltype(thread_)).name(); }
  T thread_;
};

Join () çağrısı görünüşte düzgün çalışıyor. Ancak, çözümümüzde daha güvenli bir yöntem kullanmayı tercih ettim. Bir iş parçacığı, std :: thread :: native_handle () aracılığıyla iş parçacığı alabilir. Bu tanıtıcı ile doğrudan iş parçacığına katılmak için Windows API'yi çağırabiliriz:

WaitForSingleObject(thread_.native_handle(), INFINITE);
CloseHandle(thread_.native_handle());

Daha sonra, std :: thread nesnesinin imha edilmemesi gerekir çünkü yıkıcı, ipliğe ikinci kez katılmaya çalışacaktır. Bu yüzden std :: thread nesnesini program çıkışında sallanıyoruz.


-1
2017-11-03 22:17