Main () çıktığında ayrı bir iş parçacığına ne olur?


153

Bir std::threadve daha sonra başlattığımı varsayalım detach(), böylece bir std::threadkez temsil ettiğinde, kapsam dışına çıksa bile, iş parçacığı yürütmeye devam eder.

Ayrıca, programın ayrılmış iplik 1'e katılmak için güvenilir bir protokole sahip olmadığını varsayalım , böylece ayrılmış iplik halamain() çıkarken .

Ne olması gerektiğini açıklayan standartta (daha kesin olarak, N3797 C ++ 14 taslağında) hiçbir şey bulamıyorum, ne 1.10 ne de 30.3 ilgili ifadeler içeriyor.

1 Muhtemelen eşdeğer başka bir soru şudur: "koparılmış bir iş parçacığı tekrar birleştirilebilir mi?" sinyal alma işleminden hemen sonra ipliği, alıcı ucun ipliğin gerçekten bittiğini güvenilir bir şekilde tespit etmesinin bir yolu olmadan bir saat uyku moduna geçirmeye karar verin.

Bitmek ise main()müstakil olarak çalışan iş parçacığı ile tanımsız davranıştır, o zaman herhangi kullanılması std::thread::detach()ana iş parçacığı çıkar asla sürece tanımsız davranıştır 2 .

Bu nedenle, main()koparılmış iş parçacıkları ile bitmek koşmanın tanımlanmış etkileri olmalıdır . Soru şu: nereye (içinde C ++ standart , POSIX, değil OS docs, ...) tanımlanan etkileridir.

2 Ayrılmış bir iplik birleştirilemez (anlamında std::thread::join()). Sen olabilir (örn üzerinden bir gelecek müstakil parçacığı sonuçlarını beklemek std::packaged_taskya da bir sayma semafor veya bayrak ve bir koşul değişkeni ile), ancak o değil garanti yapar iplik çalışmayı tamamlandığında . Eğer dişin ilk otomatik nesneyi yıkıcı içine sinyal parçası koymak sürece Nitekim, orada olacak , genel olarak, bu çalışma kodu (yıkıcılar) olmak sonra sinyalizasyon kodu. İşletim sistemi ana iş parçacığı sonucu tüketecek ve ayrılan iş parçacığı adı geçen yıkıcıları çalıştırmadan önce çıkacak şekilde zamanlarsa, ^ Wis ne olacağını tanımlar?


5
[Basic.start.term] / 4 içinde çok belirsiz bir notu bulabilirim: "Her iş parçacığını bir çağrıdan std::exitveya çıkıştan önce sonlandırmak mainbu gereksinimleri karşılamak için yeterlidir, ancak gerekli değildir." (paragrafın tamamı alakalı olabilir) Ayrıca bkz. [support.start.term] / 8 ( döndürüldüğünde std::exitçağrılır main)
dyp

Yanıtlar:


45

Orijinal " main()çıkışta müstakil bir iş parçacığına ne olur? "

Çalışmaya devam eder (standart durdurulduğunu söylemez) ve diğer iş parçacıklarının veya statik nesnelerin (otomatik | thread_local) değişkenlerine dokunmadığı sürece iyi tanımlanmıştır.

Bu, iş parçacığı yöneticilerinin statik nesneler olarak izin vermesine izin verildiği görülüyor ( işaretçi için @dyp sayesinde [basic.start.term] / 4 ifadesinde çok şey söylüyor).

Statik nesnelerin imhası bittiğinde sorunlar ortaya çıkar, çünkü yürütme yalnızca sinyal işleyicilerinde izin verilen kodun yürütülebileceği bir rejime girer ( [basic.start.term] / 1, 1. cümle ). C ++ standart kütüphanesinden, bu sadece <atomic>kütüphanedir ( [support.runtime] / 9, 2. cümle ). Özellikle, bu - genel olarak - kapsam dışıdır condition_variable (bunun bir sinyal işleyicide kullanılmak üzere kaydedilip kaydedilmeyeceği uygulama tanımlıdır, çünkü bir parçası değildir <atomic>).

Bu noktada yığınızı açmadıkça, tanımlanmamış davranışlardan nasıl kaçınılacağını görmek zor.

İkinci soruya "koparılmış iplikler tekrar birleştirilebilir" sorusunun cevabı:

Evet, birlikte *_at_thread_exitfonksiyonların ailesi ( notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ...).

Bir durum değişkeni ya da bir semaforu veya bir atom sayacı işaret söz konusu dipnot [2] de belirtildiği gibi, yürütme ucu sağlanması anlamında (müstakil bir iplik katılmak için yeterli değildir -oldu daha önce alınmasına söz konusu sinyal bekleyen bir sinyal tarafından gönderilir), çünkü genel olarak, örn.notify_all() bir koşul değişkeninden, özellikle de otomatik ve evre-yerel nesnelerin yıkıcılarından .

İplik (does son şey olarak sinyal iletimini Koşu sonrasında otomatik ve iş parçacığı yerel nesnelerin yıkıcılar -oldu ) ne olduğunu_at_thread_exit fonksiyonlarının aile için tasarlanmıştır.

Bu nedenle, standardın gerektirdiğinin üzerinde herhangi bir uygulama garantisi olmadığında tanımlanmamış davranışlardan kaçınmak için, _at_thread_exitsinyali veren bir işlevle (el ile) ayrı bir iş parçacığına katılmanız veya ayrılmış iş parçacığının yalnızca güvenli olacak kodu yürütmesini sağlamanız gerekir. bir sinyal işleyici de.


17
Bundan emin misin? Test ettiğim her yerde (GCC 5, clang 3.5, MSVC 14), ana iplik çıktığında tüm ayrılmış dişler öldürülür.
rustyx

3
Sorunun belirli bir uygulamanın yaptığı şey değil, standardın tanımlanmamış davranış olarak tanımladığı şeyden nasıl kaçınılacağına inanıyorum.
Jon Spencer

7
Bu cevap, statik değişkenlerin yok edilmesinden sonra, işlemin kalan ipliklerin bitmesini bekleyen bir tür uyku durumuna geçeceğini ima etmektedir. Bu doğru değildir, exitstatik nesneleri yok etme, atexitişleyicileri çalıştırma , akışları temizleme vb. İşlemleri bitirdikten sonra kontrolü ana bilgisayar ortamına döndürür, yani işlem çıkar. Ayrılmış bir iş parçacığı hala çalışıyorsa (ve kendi iş parçacığının dışındaki herhangi bir şeye dokunmadan tanımlanmamış davranıştan bir şekilde kaçındıysa), işlem çıktıkça bir duman pufunda kaybolur.
Jonathan Wakely

3
ISO olmayan C ++ API'lerini kullanarak sorun yaşıyorsanız , geri dönmek veya çağırmak yerine mainçağrı yaparsanız , işlemin ayrılmış iş parçacıklarının bitmesini beklemesine ve sonuncusu bittikten sonra çağrılmasına neden olur . pthread_exitexitexit
Jonathan Wakely

3
"Çalışmaya devam ediyor (standart durduğunu söylemediğinden)" -> Herkes bana bir iş parçacığının kapsayıcı işlemi sırasında yürütmeyi NASIL devam ettirebileceğini söyleyebilir mi?
Gupta

42

İplikleri Ayırma

Göre std::thread::detach:

Yürütme iş parçacığını iş parçacığı nesnesinden ayırır ve yürütmenin bağımsız olarak devam etmesini sağlar. Ayrılan kaynaklar iş parçacığı çıktıktan sonra serbest bırakılır.

Gönderen pthread_detach:

Pthread_detach () işlevi, uygulamaya, iş parçacığı sona erdiğinde iş parçacığı için depolamanın geri alınabileceğini göstermelidir. İş parçacığı sonlandırılmadıysa, pthread_detach () sonlandırılmasına neden olmaz. Birden çok pthread_detach () çağrısının aynı hedef iş parçacığı üzerindeki etkisi belirtilmedi.

İş parçacıklarının ayrılması, uygulamanın bir iş parçacığının bitmesini beklemesi gerekmemesi durumunda (örneğin işlem sonlandırılıncaya kadar çalışması gereken arka plan uygulamaları) kaynak tasarrufu için kullanılır:

  1. Uygulama tarafı tutamacını serbest bırakmak için: Bir std::threadnesnenin katılmadan kapsam dışı kalmasına izin verebilir , bu normalde bir çağrıya neden olurstd::terminate() imha .
  2. İşletim sisteminin, iş parçacığından çıktığı anda iş parçacığına özgü kaynakları ( TCB ) otomatik olarak temizlemesine izin vermek için , açık bir şekilde, daha sonra iş parçacığına katılmak istemediğimizi belirttik, böylece zaten ayrılmış bir iş parçacığına katılamayız.

Öldürme İplikleri

İşlem sonlandırma davranışı, en azından bazı sinyalleri yakalayabilen ana iş parçacığıyla aynıdır. Diğer iş parçacıklarının sinyalleri işleyip işleyemeyeceği önemli değildir, çünkü ana iş parçacığının sinyal işleyicisi çağrısındaki diğer iş parçacıklarına katılabilir veya sonlanabilir. (İlgili soru )

Daha önce belirtildiği gibi, ayrılmış olsun ya da olmasın , herhangi bir iş parçacığı çoğu OS'deki işlemiyle ölecektir . İşlemin kendisi bir sinyal yükseltilerek, çağrılarak exit()veya ana işlevden geri döndürülerek sonlandırılabilir . Bununla birlikte, C ++ 11, temel işletim sisteminin tam davranışını tanımlamaya çalışamaz ve tanımlamaz, oysa bir Java VM'nin geliştiricileri kesinlikle bu farklılıkları bir ölçüde soyutlayabilir. AFAIK, egzotik süreç ve iş parçacığı modelleri genellikle eski platformlarda (C ++ 11'in muhtemelen taşınamayacağı) ve özel ve / veya sınırlı dil kütüphanesi uygulaması ve sınırlı dil desteğine sahip olabilecek çeşitli gömülü sistemlerde bulunur.

Konu Desteği

Eğer iş parçacıkları desteklenmiyorsa std::thread::get_id(), std::thread::idçalıştırmak için bir iş parçacığı nesnesine ihtiyaç duymayan ve bir yapıcısının a std::threadatması gereken düz bir işlem olduğundan geçersiz bir kimlik (varsayılan olarak yapılandırılmış ) döndürmelidir std::system_error. Bugünün işletim sistemleri ile birlikte C ++ 11 bu şekilde anlıyorum. İşlemlerinde ana iş parçacığı oluşturmayan, iş parçacığı desteği olan bir işletim sistemi varsa, bana bildirin.

İplikleri Kontrol Etme

Eğer düzgün kapatma için bir iş parçacığı üzerinde kontrol tutmak gerekirse, eşitleme ilkel ve / veya bir tür bayraklar kullanarak bunu yapabilirsiniz. Ancak, bu durumda, bir kapatma bayrağını takip eden bir birleşim ayarlamak tercih ettiğim yöntemdir, çünkü kaynaklar zaten aynı anda serbest bırakılacağı için, std::threadnesnenin birkaç baytının olduğu yerlerde, iş parçacıklarını ayırarak karmaşıklığı artırmanın bir anlamı yoktur. daha yüksek karmaşıklık ve muhtemelen daha fazla senkronize ilkeller kabul edilebilir olmalıdır.


3
Her iş parçacığının kendi yığını (Linux'ta megabayt aralığında) olduğundan, iş parçacığını ayırmayı seçerim (böylece yığını çıkar çıkmaz yığını serbest bırakılır) ve ana iş parçacığının çıkması gerekiyorsa bazı eşitleme ilkelerini kullanırım (ve düzgün kapatma için geri dönen / çıkışta sonlandırmak yerine hareketsiz iş parçacıklarını birleştirmek gerekir).
Norbert Bérci

8
Bunun soruyu nasıl cevapladığını gerçekten göremiyorum
MikeMB

18

Aşağıdaki kodu göz önünde bulundurun:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

Bir Linux sisteminde çalıştırıldığında, thread_fn'den gelen mesaj asla yazdırılmaz. İşletim sistemi gerçekten çıkar thread_fn()çıkmaz temizler main(). Değiştirme t1.detach()ile t1.join()beklendiği gibi hep mesajı yazdırır.


Bu davranış, tam olarak Windows üzerinde gerçekleşir. Yani, program bittiğinde Windows'un ayrılmış iş parçacıklarını öldürdüğü anlaşılıyor.
Gupta

17

Program çıktıktan sonra iş parçacığının kaderi tanımlanmamış bir davranıştır. Ancak modern bir işletim sistemi, kapatma işleminin oluşturduğu tüm iş parçacıklarını temizleyecektir.

Bir'i ayırırken std::thread, bu üç koşul geçerli olmaya devam edecektir:

  1. *this artık hiçbir konuya sahip değil
  2. joinable() her zaman eşit olacak false
  3. get_id() eşit olacak std::thread::id()

1
Neden tanımsız? Çünkü standart hiçbir şey tanımlamıyor? Dipnotum, detach()tanımsız davranışa sahip olmak için herhangi bir çağrı yapmaz mı? İnanması zor ...
Marc Mutz - mmutz

2
@ MarcMutz-mmutz İşlem çıkarsa, ipliğin kaderinin tanımsız olması anlamsızdır.
Sezar

2
@Caesar ve iplik bitmeden çıkmamayı nasıl sağlarım?
MichalH


0

Diğer iş parçacıklarının yürütmeye devam etmesine izin vermek için, ana iş parçacığı çıkış (3) yerine pthread_exit () yöntemini çağırarak sonlandırılmalıdır. Ana pthread_exit kullanmak iyidir. Pthread_exit kullanıldığında, ana iş parçacığı yürütmeyi durdurur ve diğer tüm iş parçacıkları çıkıncaya kadar zombi (geçersiz) durumunda kalır. Ana iş parçacığında pthread_exit kullanıyorsanız, diğer iş parçacıklarının dönüş durumunu alamazsınız ve diğer iş parçacıkları için temizleme yapamazsınız (pthread_join (3) kullanılarak yapılabilir). Ayrıca, evre kaynaklarını (pthread_detach (3)) ayırmak daha iyidir, böylece evre kaynakları iş parçacığı sonlandırıldığında otomatik olarak serbest bırakılır. Tüm iş parçacıkları çıkıncaya kadar paylaşılan kaynaklar serbest bırakılmaz.


@kgvinod, neden "pthread_exit (0);" "ti.detach ()" den sonra;
yshi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.