Pertanyaan Apa arti thread_local dalam C ++ 11?


Saya bingung dengan deskripsi thread_local di C ++ 11. Pemahaman saya adalah, setiap utas memiliki salinan unik dari variabel lokal dalam suatu fungsi. Variabel global / statis dapat diakses oleh semua utas (kemungkinan sinkronisasi akses menggunakan kunci). Dan variabel thread_local terlihat ke semua utas tetapi hanya dapat dimodifikasi oleh utas yang ditetapkan? Apakah itu benar?


75
2017-08-16 09:05


asal


Jawaban:


Durasi penyimpanan lokal-thread adalah istilah yang digunakan untuk merujuk ke data yang tampaknya durasi penyimpanan global atau statis (dari sudut pandang fungsi yang menggunakannya), tetapi dalam kenyataannya, ada satu salinan per utas.

Ini menambah otomatis saat ini (ada selama blok / fungsi), statis (ada untuk durasi program) dan dinamis (ada di heap antara alokasi dan dealokasi).

Sesuatu yang merupakan thread-local dibuat pada penciptaan thread dan dibuang ketika thread berhenti.

Beberapa contoh mengikuti.

Pikirkan pembangkit bilangan acak di mana benih harus dipelihara dengan basis per-benang. Menggunakan benih thread-lokal berarti bahwa setiap utas mendapatkan urutan nomor acaknya sendiri, tidak bergantung pada untaian lainnya.

Jika benih Anda adalah variabel lokal dalam fungsi acak, itu akan diinisialisasi setiap kali Anda memanggilnya, memberi Anda nomor yang sama setiap kali. Jika itu global, benang akan mengganggu urutan masing-masing.

Contoh lain adalah sesuatu seperti strtok di mana keadaan tokenisasi disimpan secara khusus. Dengan begitu, satu utas dapat memastikan bahwa utas lainnya tidak akan mengacaukan upaya tokasinya, sementara masih dapat mempertahankan status melalui beberapa panggilan ke strtok - ini pada dasarnya membuat strtok_r (versi thread-safe) berlebihan.

Kedua contoh ini memungkinkan variabel lokal untaian ada dalam fungsi yang menggunakannya. Dalam kode pra-ulir, itu hanya akan menjadi variabel durasi penyimpanan statis dalam fungsi tersebut. Untuk utas, itu dimodifikasi untuk mengaitkan durasi penyimpanan lokal.

Contoh lain akan seperti itu errno. Anda tidak ingin modifikasi utas terpisah errno setelah salah satu panggilan Anda gagal tetapi sebelum Anda dapat memeriksa variabel, namun Anda hanya ingin satu salinan per utas.

Situs ini memiliki deskripsi yang masuk akal tentang penentu durasi penyimpanan yang berbeda.


82
2017-08-16 09:13



Ketika Anda mendeklarasikan variabel thread_local maka setiap utas memiliki salinannya sendiri. Ketika Anda merujuknya dengan nama, maka salinan yang terkait dengan rangkaian saat ini digunakan. misalnya

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;
}

Kode ini akan menampilkan "2349", "3249", "4239", "4329", "2439" atau "3429", tetapi tidak pernah ada yang lain. Setiap utas memiliki salinannya sendiri i, yang ditugaskan untuk, ditambah dan kemudian dicetak. Benangnya menyala main juga memiliki salinannya sendiri, yang ditugaskan pada awal dan kemudian ditinggalkan tidak berubah. Semua salinan ini sepenuhnya independen, dan masing-masing memiliki alamat yang berbeda.

Hanya itulah nama yang istimewa dalam hal itu --- jika Anda mengambil alamat a thread_local variabel maka Anda hanya memiliki pointer normal ke objek normal, yang dapat Anda lewati secara bebas di antara untaian. misalnya

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;
}

Karena alamat i diteruskan ke fungsi utas, lalu salinan i milik utas utama dapat ditetapkan untuk meskipun itu thread_local. Program ini akan menghasilkan "42". Jika Anda melakukan ini, maka Anda perlu berhati-hati itu *p tidak diakses setelah thread itu milik telah keluar, jika tidak Anda mendapatkan pointer yang menggantung dan perilaku tidak terdefinisi seperti halnya kasus lain di mana objek runcing dihancurkan.

thread_local Variabel diinisialisasi "sebelum penggunaan pertama", jadi jika mereka tidak pernah tersentuh oleh thread yang diberikan maka mereka tidak selalu diinisialisasi. Ini untuk memungkinkan kompiler menghindari pembuatan setiap thread_local variabel dalam program untuk utas yang sepenuhnya mandiri dan tidak menyentuh salah satu dari mereka. misalnya

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

void f(){
    thread_local my_class;
}

void do_nothing(){}

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

Dalam program ini ada 2 utas: utas utama dan utas yang dibuat secara manual. Tidak ada panggilan utas f, sehingga thread_local objek tidak pernah digunakan. Oleh karena itu tidak ditentukan apakah kompiler akan membangun 0, 1 atau 2 contoh my_class, dan hasilnya mungkin "", "hellohellogoodbyegoodbye" atau "hellogoodbye".


80
2017-08-16 10:41



Penyimpanan lokal-thread ada di setiap aspek seperti penyimpanan statis (= global), hanya bahwa setiap utas memiliki salinan terpisah dari objek. Waktu hidup objek dimulai baik di mulai thread (untuk variabel global) atau pada inisialisasi pertama (untuk statika blok-lokal), dan berakhir ketika utas berakhir (yaitu ketika join() disebut).

Akibatnya, hanya variabel yang bisa juga dideklarasikan static dapat dinyatakan sebagai thread_local, yaitu variabel global (lebih tepat: variabel "di ruang lingkup namespace"), anggota kelas statis, dan variabel statis blok (dalam hal ini static tersirat).

Sebagai contoh, misalkan Anda memiliki kumpulan untaian dan ingin tahu seberapa baik beban kerja Anda seimbang:

thread_local Counter c;

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

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

Ini akan mencetak statistik penggunaan benang, mis. dengan implementasi seperti ini:

struct Counter
{
     unsigned int c = 0;
     void increment() { ++c; }
     ~Counter()
     {
         std::cout << "Thread #" << std::this_thread::id() << " was called "
                   << c << " times" << std::endl;
     }
};

15
2017-08-16 09:23