Thread_local, C ++ 11'de ne anlama geliyor?


131

thread_localC ++ 11'deki açıklamayla kafam karıştı . Anladığım kadarıyla, her iş parçacığı bir işlevdeki yerel değişkenlerin benzersiz bir kopyasına sahiptir. Global / statik değişkenlere tüm evreler tarafından erişilebilir (muhtemelen kilitleri kullanarak senkronize erişim). Ve thread_localdeğişkenler tüm iş parçacıkları tarafından görülebilir, ancak yalnızca tanımlandıkları iş parçacığı tarafından değiştirilebilir mi? Doğru mu?

Yanıtlar:


151

İş parçacığı yerel depolama süresi, görünüşte genel veya statik depolama süresi olan (onu kullanan işlevlerin bakış açısından) verilere atıfta bulunmak için kullanılan bir terimdir, ancak gerçekte, iş parçacığı başına bir kopya vardır.

Geçerli otomatiğe (bir blok / işlev sırasında var), statik (program süresi boyunca var) ve dinamik (ayırma ve serbest bırakma arasındaki yığın üzerinde var) ekler.

İş parçacığı yerel olan bir şey iş parçacığı yaratılırken ortaya çıkar ve iş parçacığı durduğunda atılır.

Bazı örnekler aşağıdadır.

Çekirdeğin iş parçacığı bazında tutulması gereken rastgele bir sayı üreteci düşünün. İş parçacığı yerel çekirdek kullanmak, her iş parçacığının diğer iş parçacıklarından bağımsız olarak kendi rasgele sayı dizisini alması anlamına gelir.

Tohumunuz rastgele fonksiyon içinde yerel bir değişken olsaydı, onu her çağırdığınızda başlatılır ve size her seferinde aynı sayıyı verir. Global olsaydı, iplikler birbirlerinin dizilerine müdahale ederdi.

Başka bir örnek, strtokbelirteçleştirme durumunun iş parçacığına özgü bir temelde depolandığı bir şeydir . Bu şekilde, tek bir iş parçacığı, diğer iş parçacığının tokenizasyon çabalarını mahvetmeyeceğinden emin olurken, aynı zamanda birden fazla çağrı üzerinden durumu koruyabilir strtok- bu temelde strtok_r(iş parçacığı güvenli sürüm) gereksiz hale getirir .

Her iki örnek de iş parçacığı yerel değişkeninin onu kullanan işlev içinde var olmasına izin verir . Önceden iş parçacıklı kodda, işlev içinde basitçe bir statik depolama süresi değişkeni olacaktır. İş parçacıkları için, yerel depolama süresini işlemek için değiştirildi.

Yine başka bir örnek şöyle bir şey olabilir errno. errnoÇağrılarınızdan biri başarısız olduktan sonra, değişkeni kontrol etmeden önce ayrı iş parçacıkları değiştirmek istemiyorsunuz ve yine de iş parçacığı başına yalnızca bir kopya istiyorsunuz.

Bu site , farklı depolama süresi belirleyicilerinin makul bir açıklamasına sahiptir.


4
Yerel iş parçacığı kullanmak, ile ilgili sorunları çözmez strtok. strtoktek iş parçacıklı bir ortamda bile kırılır.
James Kanze

11
Özür dilerim, bunu yeniden ifade edeyim. Herhangi tanıtmak gelmez yeni :-) strtok ile ilgili sorunlar
paxdiablo

7
Aslında, ripliğin güvenliğiyle hiçbir ilgisi olmayan "yeniden giren" anlamına gelir. İş parçacığı yerel depolamayla bazı şeylerin iş parçacığı güvenli bir şekilde çalışmasını sağlayabileceğiniz doğrudur, ancak bunları yeniden giriş yapamazsınız.
Kerrek SB

5
Tek iş parçacıklı bir ortamda, işlevlerin yalnızca çağrı grafiğindeki bir döngünün parçası olmaları durumunda yeniden girilmesi gerekir. Bir yaprak işlevi (diğer işlevleri çağırmayan), tanım gereği bir döngünün parçası değildir ve strtokdiğer işlevleri çağırmak için iyi bir neden yoktur .
MSalters

3
bu her şeyi berbat eder: while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
japreiss

135

Bir değişken bildirdiğinizde, thread_localher evre kendi kopyasına sahip olur. Adına atıfta bulunduğunuzda, mevcut iş parçacığı ile ilişkili kopya kullanılır. Örneğin

thread_local int i=0;

void f(int newval){
    i=newval;
}

void g(){
    std::cout<<i;
}

void threadfunc(int id){
    f(id);
    ++i;
    g();
}

int main(){
    i=9;
    std::thread t1(threadfunc,1);
    std::thread t2(threadfunc,2);
    std::thread t3(threadfunc,3);

    t1.join();
    t2.join();
    t3.join();
    std::cout<<i<<std::endl;
}

Bu kod "2349", "3249", "4239", "4329", "2439" veya "3429" çıktılar, ancak başka hiçbir şey vermez. Her iş parçacığının i, atanan, artırılan ve ardından yazdırılan kendi kopyası vardır . İş parçacığı çalışıyormain da başlangıçta atanan ve sonra değiştirilmeden bırakılan kendi kopyası vardır. Bu kopyalar tamamen bağımsızdır ve her birinin farklı bir adresi vardır.

Sadece bu açıdan özel olan isimdir --- eğer bir thread_localdeğişkenin adresini alırsanız, o zaman normal bir nesneye normal bir göstericiniz olur, bu iplikler arasında serbestçe geçebilirsiniz. Örneğin

thread_local int i=0;

void thread_func(int*p){
    *p=42;
}

int main(){
    i=9;
    std::thread t(thread_func,&i);
    t.join();
    std::cout<<i<<std::endl;
}

Adresi ievre işlevine aktarıldığından, bu idurumda bile ana evreye ait olanın kopyası atanabilir thread_local. Bu program böylece "42" çıktısını alacaktır. Bunu yaparsanız, *pait olduğu iş parçacığı çıktıktan sonra erişilemeyenlere dikkat etmeniz gerekir , aksi takdirde, işaret edilen nesnenin yok edildiği diğer durumlarda olduğu gibi sarkan bir işaretçi ve tanımsız davranış elde edersiniz.

thread_localdeğişkenler "ilk kullanımdan önce" ilklendirilir, bu nedenle belirli bir iş parçacığına hiç dokunulmazlarsa, o zaman mutlaka ilklendirilmeleri gerekmez. Bu, derleyicilerin thread_localprogramdaki her değişkeni tamamen kendi kendine yeten ve hiçbirine dokunmayan bir iş parçacığı için yapılandırmaktan kaçınmasını sağlamak içindir . Örneğin

struct my_class{
    my_class(){
        std::cout<<"hello";
    }
    ~my_class(){
        std::cout<<"goodbye";
    }
};

void f(){
    thread_local my_class unused;
}

void do_nothing(){}

int main(){
    std::thread t1(do_nothing);
    t1.join();
}

Bu programda 2 iş parçacığı vardır: ana iş parçacığı ve elle oluşturulan iş parçacığı. Hiçbir iş parçacığı çağırmaz f, dolayısıyla thread_localnesne asla kullanılmaz. Bu nedenle, derleyicinin 0, 1 veya 2 örneklerini oluşturup oluşturmayacağı belirtilmemiştir my_classve çıktı "", "hellohellogoodbyegoodbye" veya "hellogoodbye" olabilir.


1
Değişkenin iş parçacığı yerel kopyasının değişkenin yeni başlatılmış bir kopyası olduğuna dikkat etmenin önemli olduğunu düşünüyorum. Eğer bir ekleme Yani, eğer g()başlangıcına çağrısını threadFuncsonra çıkış olacak, 0304029ya da çiftlerinin bazı diğer permütasyon 02, 03ve 04. Yani 9 atanmış olsa bile, bir iiş parçacığı oluşturulmadan önce, iplikler bir taze inşa kopyasını almak . İle atanmışsa , her iş parçacığı yeni bir rastgele tamsayı alır. ii=0ithread_local int i = random_integer()
Mark H

Tam olarak değil bir permütasyon 02, 03, 04, gibi diğer diziler olabilir020043
Hongxu Chen

Az önce bulduğum ilginç bilgi: GCC, bir thread_local değişkeninin adresini şablon argümanı olarak kullanmayı destekliyor, ancak diğer derleyiciler desteklemiyor (bu yazı itibariyle; clang, vstudio denendi). Standardın bu konuda ne söylemesi gerektiğinden veya burası belirtilmemiş bir alan olup olmadığından emin değilim.
jwd

23

İş parçacığı yerel depolama, her açıdan statik (= global) depolama gibi, yalnızca her iş parçacığının nesnenin ayrı bir kopyası vardır. Nesnenin yaşam süresi, iş parçacığı başlangıcında (genel değişkenler için) veya ilk başlatmada (blok-yerel statikler için) başlar ve iş parçacığı sona erdiğinde (örn.join() çağrıldığında) .

Sonuç olarak, yalnızca staticbildirilebilen thread_localdeğişkenler, yani global değişkenler (daha doğrusu: "ad alanı kapsamındaki değişkenler"), statik sınıf üyeleri ve blok-statik değişkenler (bu durumdastatic ima edilir) olarak bildirilebilir.

Örnek olarak, bir iş parçacığı havuzunuz olduğunu ve iş yükünüzün ne kadar iyi dengelendiğini bilmek istediğinizi varsayalım:

thread_local Counter c;

void do_work()
{
    c.increment();
    // ...
}

int main()
{
    std::thread t(do_work);   // your thread-pool would go here
    t.join();
}

Bu, aşağıdaki gibi bir uygulama ile iş parçacığı kullanım istatistiklerini yazdıracaktır:

struct Counter
{
     unsigned int c = 0;
     void increment() { ++c; }
     ~Counter()
     {
         std::cout << "Thread #" << std::this_thread::id() << " was called "
                   << c << " times" << std::endl;
     }
};
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.