Cout senkronize mi / iş parçacığı güvenli mi?


112

Genel olarak akışların senkronize edilmediğini varsayıyorum, uygun kilitlemeyi yapmak kullanıcıya kalmıştır. Ancak, coutstandart kitaplıkta özel muamele görmek gibi şeyler var mı?

Yani, birden fazla iş parçacığı yazıyorsa cout, coutnesneyi bozabilirler mi? Senkronize edilmiş olsanız bile yine de rastgele serpiştirilmiş çıktı alacağınızı anlıyorum, ancak bu serpiştirme garantilidir. Yani, coutbirden çok iş parçacığından kullanmak güvenli midir?

Bu satıcıya bağımlı mı? Gcc ne yapar?


Önemli : Bunun için bir tür kanıta ihtiyacım olduğundan, "evet" derseniz, cevabınız için lütfen bir tür referans sağlayın.

Benim endişem aynı zamanda temeldeki sistem çağrıları ile ilgili değil, bunlar sorun değil, ancak akışlar üstüne bir ara bellek katmanı ekliyor.


2
Bu satıcıya bağlıdır. C ++ (C ++ 0x'ten önce) birden çok iş parçacığı kavramına sahip değildir.
Sven

2
Peki ya c ++ 0x? Bir bellek modelini tanımlar ve iplik nedir, bu yüzden belki bu şeyler çıktıda damladı?
rubenvb

2
İş parçacığı açısından güvenli hale getiren herhangi bir satıcı var mı?
edA-qa mort-ora-y

C ++ 2011 tarafından önerilen en son standartla bağlantısı olan var mı?
edA-qa mort-ora-y

4
Bir anlamda , tam çıktının tek seferde yazılmasıyla burası printfparlarstdout ; std::coutifade zincirinin her bir bağlantısı kullanıldığında , ayrı ayrı çıktı alınacaktır stdout; bunların arasında stdout, son çıktının sırasının karıştığı için başka bir iş parçacığı yazısı olabilir .
legends2k

Yanıtlar:


106

C ++ 03 standardı bu konuda hiçbir şey söylemiyor. Bir şeyin iş parçacığı güvenliği konusunda hiçbir garantiniz olmadığında, onu iş parçacığı için güvenli değil olarak değerlendirmelisiniz.

Burada özellikle ilgi çekici olan cout, tamponlanmış olan gerçektir . Çağrılar write(veya bu belirli uygulamada bu etkiyi gerçekleştiren her ne ise) karşılıklı olarak dışlanacağı garanti edilse bile , ara bellek farklı evreler tarafından paylaşılabilir. Bu, akışın iç durumunun hızla bozulmasına yol açacaktır.

Ve arabelleğe erişimin iş parçacığı açısından güvenli olduğu garanti edilse bile, bu kodda ne olacağını düşünüyorsunuz?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

Muhtemelen buradaki her satırın karşılıklı dışlama içinde hareket etmesini istiyorsunuz. Fakat bir uygulama bunu nasıl garanti edebilir?

C ++ 11'de bazı garantilerimiz var. FDIS, §27.4.1 [iostream.objects.overview] 'da şunları söylüyor:

Senkronize (§27.5.3.4) standart bir iostream nesnesinin biçimlendirilmiş ve biçimlendirilmemiş giriş (§27.7.2.1) ve çıktı (§27.7.3.1) işlevlerine veya birden çok iş parçacığı tarafından standart bir C akışına eşzamanlı erişim, bir veri yarışıyla sonuçlanmayacaktır (§ 1.10). [Not: Kullanıcılar, karakterlerin araya eklenmesinden kaçınmak istiyorlarsa, bu nesnelerin ve akışların birden çok iş parçacığı tarafından eş zamanlı kullanımını yine de senkronize etmelidir. - son not]

Bu nedenle, bozuk akışlar almazsınız, ancak çıktının gereksiz olmasını istemiyorsanız yine de manuel olarak senkronize etmeniz gerekir.


2
C ++ 98 / C ++ 03 için teknik olarak doğru, ancak herkesin bunu bildiğini düşünüyorum. Ancak bu, iki ilginç soruyu yanıtlamaz: Peki ya C ++ 0x? Tipik uygulamalar aslında ne yapar ?
Nemo

1
@ edA-qa mort-ora-y: Hayır, yanlış anladınız. C ++ 11 açık bir şekilde, standart akış nesneler olduğunu tanımlar olabilir senkronize ve varsayılan olarak değil, iyi tanımlanmış davranışı muhafaza edilir.
ildjarn

12
@ildjarn - Hayır, @ edA-qa mort-ora-y doğru. cout.sync_with_stdio()Doğru olduğu sürece , coutek senkronizasyon olmadan birden çok iş parçacığından karakterlerin çıktısının kullanılması iyi tanımlanmıştır, ancak yalnızca bireysel bayt düzeyinde. Bu nedenle, cout << "ab";ve cout << "cd"farklı evrelerde Mayıs çıkış yürütülen acdbörneğin olmayıp örneğin tanımlanmamış bir davranışa neden.
JohannesD

4
@JohannesD: Orada hemfikiriz - temeldeki C API ile senkronize edildi. Demek istediğim, yararlı bir şekilde "senkronize edilmemiş" olmasıdır, yani gereksiz verileri istemiyorlarsa yine de manuel senkronizasyona ihtiyaç duyarlar.
ildjarn

2
@ildjarn, anladığım kadarıyla çöp verileriyle iyiyim. Şimdi net görünen veri yarış durumu ile ilgileniyorum.
edA-qa mort-ora-y

16

Bu harika bir soru.

İlk olarak, C ++ 98 / C ++ 03 "iş parçacığı" kavramına sahip değildir. Yani bu dünyada soru anlamsız.

C ++ 0x ne olacak? Martinho'nun cevabına bakın (itiraf ediyorum ki beni şaşırttı).

C ++ 0x öncesi özel uygulamalara ne dersiniz? Örneğin, işte basic_streambuf<...>:sputcGCC 4.5.2'deki kaynak kodu ("streambuf" başlığı):

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

Açıkça, bu kilitleme yapmaz. Ve hiç yapmıyor xsputn. Ve bu kesinlikle cout'un kullandığı streambuf türüdür.

Anlayabildiğim kadarıyla, libstdc ++ herhangi bir akış işleminin etrafında kilitleme yapmıyor. Ve bu yavaş olacağı için hiç beklemiyorum.

Dolayısıyla, bu uygulama ile, açıkça iki iş parçacığının çıktısının birbirini bozması mümkündür ( sadece serpiştirmek değil ).

Bu kod veri yapısını bozabilir mi? Cevap, bu işlevlerin olası etkileşimlerine bağlıdır; örneğin, bir iş parçacığı tamponu temizlemeye çalışırken diğeri çağırmaya çalışırsa xsputnne olur? Derleyicinizin ve CPU'nuzun bellek yüklerini ve depolarını yeniden sıralamaya nasıl karar verdiğine bağlı olabilir; emin olmak için dikkatli bir analiz gerekir. Ayrıca, iki iş parçacığının aynı konumu eşzamanlı olarak değiştirmeye çalışması CPU'nuzun ne yapacağına da bağlıdır.

Diğer bir deyişle, mevcut ortamınızda düzgün çalışsa bile, çalışma zamanınız, derleyiciniz veya CPU'nuzdan herhangi birini güncellediğinizde bozulabilir.

Yönetici özeti: "Yapmazdım". Düzgün kilitleme yapan veya C ++ 0x'e geçen bir günlük sınıfı oluşturun.

Zayıf bir alternatif olarak, cout'u tamponsuz olarak ayarlayabilirsiniz. Bu muhtemelen (garanti edilmese de) tamponla ilgili tüm mantığı atlayacak ve writedoğrudan çağıracaktır . Her ne kadar bu çok yavaş olabilir.


1
İyi yanıt, ancak Martinho'nun C ++ 11'in senkronizasyonu tanımladığını gösteren yanıtına bakın cout.
edA-qa mort-ora-y

7

C ++ Standardı, akışlara yazmanın iş parçacığı açısından güvenli olup olmadığını belirtmez, ancak genellikle değildir.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

ve ayrıca: Standart çıktı akışları C ++ iş parçacığı açısından güvenli midir (cout, Cerr, clog)?

GÜNCELLEME

Yeni standart C ++ 11'in bu konuda ne söylediğini öğrenmek için lütfen @Martinho Fernandes'nin cevabına bir göz atın.


3
Sanırım C ++ 11 artık standart olduğu için bu cevap aslında yanlış.
edA-qa mort-ora-y

6

Diğer yanıtların da belirttiği gibi, bu kesinlikle satıcıya özgüdür çünkü C ++ standardı iş parçacığından bahsetmez (bu, C ++ 0x'de değişir).

GCC, iş parçacığı güvenliği ve I / O hakkında çok fazla söz vermez. Ancak vaat ettiği şeyin belgeleri burada:

Muhtemelen anahtar konu şudur:

__Basic_file türü, C stdio katmanının etrafındaki küçük sarmalayıcıların bir koleksiyonudur (yine, Yapı altındaki bağlantıya bakın). Kendimizi kilitlemiyoruz, sadece fopen, fwrite vb. Çağrılara geçiyoruz.

Öyleyse, 3.0 için, "G / Ç için çoklu okuma güvenli mi" sorusu yanıtlanmalıdır, "platformunuzun C kitaplığı G / Ç için güvenli mi?" Bazıları varsayılan olarak, bazıları değildir; birçoğu, değişen iş parçacığı güvenliği ve verimliliği ödünleşimleriyle birlikte C kitaplığının çoklu uygulamalarını sunar. Siz, programcı, her zaman birden fazla iş parçacığı ile ilgilenmeniz gerekir.

(Örnek olarak, POSIX standardı, C stdio FILE * işlemlerinin atomik olmasını gerektirir. POSIX uyumlu C kitaplıklarında (örneğin, Solaris ve GNU / Linux üzerinde) FILE * lerde işlemleri serileştirmek için dahili bir mutex vardır. Bununla birlikte, yine de ihtiyacınız var bir iş parçacığında fclose (fs) çağırmak ve ardından diğerinde fs erişimi gibi aptalca şeyler yapmamak.)

Dolayısıyla, platformunuzun C kitaplığı iş parçacığı açısından güvenli ise, fstream I / O işlemleriniz en düşük seviyede iş parçacığı açısından güvenli olacaktır. Akış biçimlendirme sınıflarında bulunan verileri değiştirmek gibi daha yüksek seviyeli işlemler için (örneğin, bir std :: ofstream içinde geri aramaların ayarlanması), bu tür erişimleri diğer kritik paylaşılan kaynaklar gibi korumanız gerekir.

Bahsedilen 3.0 zaman diliminde herhangi bir şeyin değişip değişmediğini bilmiyorum.

MSVC'nin iş parçacığı güvenlik belgeleri iostreamsşurada bulunabilir: http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx :

Tek bir nesne, birden çok iş parçacığından okumak için iş parçacığı güvenlidir. Örneğin, bir A nesnesi verildiğinde, A'yı eşzamanlı olarak iplik 1'den ve 2'den okumak güvenlidir.

Tek bir nesneye tek bir iş parçacığı tarafından yazılıyorsa, o nesneye aynı veya diğer iş parçacığı üzerindeki tüm okuma ve yazma işlemleri korunmalıdır. Örneğin, bir A nesnesi verildiğinde, eğer 1. iplik A'ya yazıyorsa, 2. ipliğin A'dan okuması veya yazması engellenmelidir.

Başka bir iş parçacığı aynı türün farklı bir örneğini okuyor veya yazıyor olsa bile, bir türün bir örneğini okumak ve yazmak güvenlidir. Örneğin, aynı türden A ve B nesneleri verildiğinde, A evre 1'de yazılıyorsa ve B evre 2'de okunuyorsa güvenlidir.

...

iostream Sınıfları

İostream sınıfları, bir istisna dışında diğer sınıflarla aynı kuralları izler. Birden çok iş parçacığından bir nesneye yazmak güvenlidir. Örneğin, evre 1, evre 2 ile aynı anda cout'a yazabilir. Bununla birlikte, bu, iki evrenin birbirine karıştırılmasıyla sonuçlanabilir.

Not: Bir akış arabelleğinden okumak, bir okuma işlemi olarak kabul edilmez. Bir yazma işlemi olarak düşünülmelidir çünkü bu sınıfın durumunu değiştirir.

Bu bilgilerin cl.exeMSVC'nin en son sürümü için olduğunu unutmayın (şu anda VS 2010 / MSVC 10 / 16.x için). Sayfadaki açılır bir kontrol kullanarak MSVC'nin eski sürümleri için bilgileri seçebilirsiniz (ve bilgiler eski sürümler için farklıdır).


1
"Bahsedilen 3.0 zaman diliminde herhangi bir şeyin değişip değişmediğini bilmiyorum." Kesinlikle oldu. Geçtiğimiz birkaç yıl boyunca, g ++ akış gerçeklemesi kendi arabelleğini oluşturdu.
Nemo
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.