Kontainer dan Objek Paralel

Pustaka Pola Paralel (PPL) mencakup beberapa kontainer dan objek yang menyediakan akses aman utas ke elemennya.

Kontainer konkuren menyediakan akses yang aman untuk konkurensi ke operasi yang paling penting. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Fungsionalitas kontainer ini menyerupai kontainer yang disediakan oleh Pustaka Standar C++. Misalnya, kelas konkurensi::concurrent_vector menyerupai kelas std::vector , kecuali bahwa concurrent_vector kelas memungkinkan Anda menambahkan elemen secara paralel. Gunakan kontainer bersamaan saat Anda memiliki kode paralel yang memerlukan akses baca dan tulis ke kontainer yang sama.

Objek konkuren digunakan secara bersamaan di antara komponen. Proses yang menghitung status objek bersamaan secara paralel menghasilkan hasil yang sama dengan proses lain yang menghitung status yang sama secara serial. Kelas konkurensi::combinable adalah salah satu contoh tipe objek konkuren. Kelas ini combinable memungkinkan Anda melakukan komputasi secara paralel, lalu menggabungkan komputasi tersebut menjadi hasil akhir. Gunakan objek bersamaan saat Anda akan menggunakan mekanisme sinkronisasi, misalnya, mutex, untuk menyinkronkan akses ke variabel atau sumber daya bersama.

Bagian

Topik ini menjelaskan kontainer dan objek paralel berikut secara rinci.

Kontainer bersamaan:

Objek bersamaan:

Kelas concurrent_vector

Kelas konkurensi::concurrent_vector adalah kelas kontainer urutan yang, sama seperti kelas std::vector , memungkinkan Anda mengakses elemennya secara acak. Kelas concurrent_vector memungkinkan operasi penambahan dan akses elemen yang aman untuk konkurensi. Operasi penambahan tidak membatalkan penunjuk atau iterator yang ada. Akses iterator dan operasi traversal juga aman dari keserentakan. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu.

Perbedaan Antara concurrent_vector dan vector

Kelas concurrent_vector ini sangat menyerupai vector kelas. Kompleksitas operasi penambahan, akses elemen, dan akses iterator pada objek concurrent_vector adalah sama dengan pada objek vector. Poin-poin berikut menggambarkan di mana concurrent_vector berbeda dari vector:

  • Penambahan, akses elemen, akses iterator, dan operasi traversal iterator pada objek concurrent_vector yang aman terhadap konkurensi.

  • Anda hanya dapat menambahkan elemen ke akhir concurrent_vector objek. Kelas concurrent_vector tidak menyediakan insert metode .

  • Objek concurrent_vector tidak menggunakan semantik pemindahan saat Anda menambahkannya.

  • Kelas concurrent_vector tidak menyediakan erase metode atau pop_back . vector Seperti halnya, gunakan metode clear untuk menghapus semua elemen dari objek concurrent_vector.

  • Kelas concurrent_vector tidak menyimpan elemennya secara berdekatan dalam memori. Oleh karena itu, Anda tidak dapat menggunakan kelas concurrent_vector dengan semua cara yang Anda bisa gunakan untuk array. Misalnya, untuk variabel bernama v jenis concurrent_vector, ekspresi &v[0]+2 menghasilkan perilaku yang tidak ditentukan.

  • Kelas concurrent_vector mendefinisikan metode grow_by dan grow_to_at_least. Metode ini menyerupai metode mengubah ukuran , kecuali bahwa metode tersebut aman konkurensi.

  • Objek concurrent_vector tidak merelokasi elemennya saat Anda menambahkan atau mengubah ukurannya. Ini memungkinkan pointer dan iterator yang ada untuk tetap valid selama operasi bersamaan.

  • Runtime tidak menentukan versi khusus untuk jenis concurrent_vectorbool.

Operasi Konkurensi-Aman

Semua metode yang menambahkan atau meningkatkan ukuran concurrent_vector objek, atau mengakses elemen dalam concurrent_vector objek, aman konkurensi. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Pengecualian untuk aturan ini adalah resize metode .

Tabel berikut menunjukkan metode dan operator umum concurrent_vector yang aman konkurensi.

Operasi yang disediakan runtime untuk kompatibilitas dengan Pustaka Standar C++, misalnya, reserve, tidak aman untuk digunakan secara bersamaan. Tabel berikut menunjukkan metode dan operator umum yang tidak aman konkurensi.

Operasi yang memodifikasi nilai elemen yang ada tidak aman konkurensi. Gunakan objek sinkronisasi seperti objek reader_writer_lock untuk menyinkronkan operasi baca dan tulis bersamaan ke elemen data yang sama. Untuk informasi selengkapnya tentang objek sinkronisasi, lihat Struktur Data Sinkronisasi.

Ketika Anda mengonversi kode yang ada yang menggunakan vector untuk menggunakan concurrent_vector, operasi bersamaan dapat menyebabkan perilaku aplikasi Anda berubah. Misalnya, pertimbangkan program berikut yang secara bersamaan melakukan dua tugas pada objek concurrent_vector . Tugas pertama menambahkan elemen tambahan ke concurrent_vector objek. Tugas kedua menghitung jumlah semua elemen dalam objek yang sama.

// parallel-vector-sum.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create a concurrent_vector object that contains a few
   // initial elements.
   concurrent_vector<int> v;
   v.push_back(2);
   v.push_back(3);
   v.push_back(4);
   
   // Perform two tasks in parallel.
   // The first task appends additional elements to the concurrent_vector object.
   // The second task computes the sum of all elements in the same object.

   parallel_invoke(
      [&v] { 
         for(int i = 0; i < 10000; ++i)
         {
            v.push_back(i);
         }
      },
      [&v] {
         combinable<int> sums;
         for(auto i = begin(v); i != end(v); ++i) 
         {
            sums.local() += *i;
         }     
         wcout << L"sum = " << sums.combine(plus<int>()) << endl;
      }
   );
}

Meskipun metode end aman untuk konkurensi, panggilan bersamaan ke metode push_back menyebabkan nilai yang dikembalikan oleh end berubah. Jumlah elemen yang dilalui iterator tidak ditentukan. Oleh karena itu, program ini dapat menghasilkan hasil yang berbeda setiap kali Anda menjalankannya. Ketika jenis elemen tidak sepele, dimungkinkan adanya race condition antara panggilan push_back dan end. Metode end ini dapat mengembalikan elemen yang dialokasikan, tetapi tidak sepenuhnya diinisialisasi.

Keselamatan Pengecualian

Jika operasi pertumbuhan atau penugasan melempar pengecualian, status concurrent_vector objek menjadi tidak valid. Perilaku concurrent_vector objek yang dalam status tidak valid tidak terdefinisi kecuali dinyatakan lain. Namun, destruktor selalu membebaskan memori yang dialokasikan oleh objek, meskipun objek berada dalam keadaan tidak valid.

Jenis data elemen vektor, T, harus memenuhi persyaratan berikut. Jika tidak, perilaku kelas concurrent_vector tidak terdefinisi.

  • Destruktor tidak boleh melempar.

  • Jika konstruktor default atau salin dilemparkan, destruktor tidak boleh dideklarasikan dengan menggunakan virtual kata kunci dan harus bekerja dengan benar dengan memori yang diinisialisasi nol.

[Atas]

Kelas concurrent_queue

Kelas konkurensi::concurrent_queue , sama seperti kelas std::queue , memungkinkan Anda mengakses elemen depan dan belakangnya. Kelas ini concurrent_queue memungkinkan operasi antrean dan penghapusan antrean yang aman konkurensi. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Kelas concurrent_queue juga menyediakan dukungan iterator yang tidak aman dari persaingan.

Perbedaan Antara concurrent_queue dan antrean

Kelas concurrent_queue ini sangat menyerupai queue kelas. Poin-poin berikut menggambarkan di mana concurrent_queue berbeda dari queue:

  • Operasi antrean dan penghapusan antrean pada concurrent_queue objek aman konkurensi.

  • Kelas concurrent_queue menyediakan dukungan iterator yang tidak aman konkurensi.

  • Kelas concurrent_queue tidak menyediakan front metode atau pop . Kelas concurrent_queue menggantikan metode ini dengan mendefinisikan metode try_pop .

  • Kelas concurrent_queue tidak menyediakan back metode . Oleh karena itu, Anda tidak dapat mereferensikan akhir antrean.

  • Kelas concurrent_queue menyediakan metode unsafe_size alih-alih metode size. Metode unsafe_size ini tidak aman konkurensi.

Operasi Konkurensi-Aman

Semua metode yang digunakan untuk memasukkan ke dalam atau mengeluarkan dari antrean concurrent_queue objek bersifat aman untuk konkurensi. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu.

Tabel berikut menunjukkan metode dan operator umum concurrent_queue yang aman konkurensi.

Meskipun empty metode ini aman untuk konkurensi, operasi bersamaan dapat menyebabkan antrean bertambah atau berkurang sebelum metode empty selesai.

Tabel berikut menunjukkan metode dan operator umum yang tidak aman konkurensi.

Dukungan Iterator

menyediakan concurrent_queue iterator yang tidak aman untuk konkurensi. Kami menyarankan agar Anda menggunakan iterator ini hanya untuk debugging.

Iterator concurrent_queue hanya melintasi elemen dalam arah maju. Tabel berikut ini memperlihatkan operator yang didukung setiap iterator.

Pengoperasi Deskripsi
operator++ Maju ke item berikutnya dalam antrean. Operator ini kelebihan beban untuk menyediakan semantik pra-kenaikan dan pasca-kenaikan.
operator* Mengambil referensi ke item saat ini.
operator-> Mengambil penunjuk ke item saat ini.

[Atas]

Kelas concurrent_unordered_map

Kelas concurrency::concurrent_unordered_map adalah kelas kontainer asosiatif yang, seperti std::unordered_map, mengontrol urutan elemen berbentuk std::pair<const Key, Ty> dalam ukuran yang dapat bervariasi. Anggap peta yang tidak diurutkan sebagai kamus yang dapat Anda tambahkan pasangan kunci dan nilainya atau cari nilai menurut kunci. Kelas ini berguna ketika Anda memiliki beberapa utas atau tugas yang harus mengakses kontainer bersamaan secara bersamaan, menyisipkan ke dalamnya, atau memperbaruinya.

Contoh berikut menunjukkan struktur dasar untuk menggunakan concurrent_unordered_map. Contoh ini menyisipkan kunci karakter dalam rentang ['a', 'i']. Karena urutan operasi tidak ditentukan, nilai akhir untuk setiap kunci juga tidak ditentukan. Namun, aman untuk melakukan penyisipan secara paralel.

// unordered-map-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the map in parallel.

    concurrent_unordered_map<char, int> map; 

    parallel_for(0, 1000, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i].
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 751] [i, 755] [a, 756] [c, 758] [g, 753] [f, 752] [b, 757] [d, 750] [h, 754]
*/

Untuk contoh yang menggunakan concurrent_unordered_map untuk melakukan operasi pemetaan dan reduksi secara paralel, baca Cara Melakukan Operasi Pemetaan dan Reduksi secara Paralel.

Perbedaan antara concurrent_unordered_map dan unordered_map

Kelas concurrent_unordered_map ini sangat menyerupai unordered_map kelas. Poin-poin berikut menggambarkan di mana concurrent_unordered_map berbeda dari unordered_map:

  • Metode erase, bucket, bucket_count, dan bucket_size masing-masing diberi nama unsafe_erase, , unsafe_bucketunsafe_bucket_count, dan unsafe_bucket_size. Konvensi unsafe_ penamaan menunjukkan bahwa metode ini tidak aman konkurensi. Untuk informasi selengkapnya tentang keselamatan konkurensi, lihat Operasi Konkurensi-Aman.

  • Operasi sisipkan tidak membatalkan penunjuk atau iterator yang ada, juga tidak mengubah urutan item yang sudah ada di peta. Operasi sisipkan dan melintasi dapat terjadi secara bersamaan.

  • concurrent_unordered_map hanya mendukung iterasi ke depan.

  • Penyisipan tidak membatalkan atau memperbarui iterator yang dikembalikan oleh equal_range. Penyisipan dapat menambahkan item yang tidak sama ke akhir rentang. Iterator 'begin' menunjuk ke item yang sama.

Untuk membantu menghindari kebuntuan, tidak ada metode concurrent_unordered_map yang menahan kunci saat memanggil alokator memori, fungsi hash, atau kode lain yang ditentukan pengguna. Selain itu, Anda harus memastikan bahwa fungsi hash selalu mengevaluasi kunci yang sama dengan nilai yang sama. Fungsi hash terbaik mendistribusikan kunci secara seragam di seluruh ruang kode hash.

Operasi Konkurensi-Aman

Kelas ini concurrent_unordered_map memungkinkan operasi sisipan dan akses elemen yang aman konkurensi. Operasi sisipkan tidak membatalkan penunjuk atau iterator yang ada. Akses iterator dan operasi traversal juga aman dari keserentakan. Di sini, aman secara konkurensi berarti penunjuk atau pengulang selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Tabel berikut menunjukkan metode dan operator yang umum digunakan concurrent_unordered_map yang aman konkurensi.

count Meskipun metode ini dapat dipanggil dengan aman dari utas yang berjalan bersamaan, utas yang berbeda dapat menerima hasil yang berbeda jika nilai baru secara bersamaan dimasukkan ke dalam kontainer.

Tabel berikut menunjukkan metode dan operator yang umum digunakan yang tidak aman konkurensi.

Selain metode ini, metode apa pun yang dimulai dengan unsafe_ juga tidak aman konkurensi.

[Atas]

Kelas concurrent_unordered_multimap

Kelas konkurensi::concurrent_unordered_multimap sangat menyerupai concurrent_unordered_map kecuali bahwa kelas ini memungkinkan beberapa nilai untuk dipetakan ke kunci yang sama. Ini juga berbeda dari concurrent_unordered_map dengan cara-cara berikut:

  • Metode concurrent_unordered_multimap::insert mengembalikan iterator alih-alih std::pair<iterator, bool>.

  • Kelas concurrent_unordered_multimap tidak menyediakan operator[] atau at metode .

Contoh berikut menunjukkan struktur dasar untuk menggunakan concurrent_unordered_multimap. Contoh ini menyisipkan kunci karakter dalam rentang ['a', 'i']. concurrent_unordered_multimap memungkinkan kunci memiliki beberapa nilai.

// unordered-multimap-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the map in parallel.

    concurrent_unordered_multimap<char, int> map; 

    parallel_for(0, 10, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i].
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 4] [i, 8] [a, 9] [a, 0] [c, 2] [g, 6] [f, 5] [b, 1] [d, 3] [h, 7]
*/

[Atas]

Kelas concurrent_unordered_set

Kelas konkurensi::concurrent_unordered_set mirip dengan concurrent_unordered_map kelas, kecuali bahwa ia mengelola elemen alih-alih pasangan kunci dan nilai. Kelas concurrent_unordered_set tidak menyediakan operator[] atau at metode .

Contoh berikut menunjukkan struktur dasar untuk menggunakan concurrent_unordered_set. Contoh ini menyisipkan nilai karakter dalam rentang ['a', 'i']. Melakukan penyisipan secara paralel adalah aman.

// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the set in parallel.

    concurrent_unordered_set<char> set; 

    parallel_for(0, 10000, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [i] [a] [c] [g] [f] [b] [d] [h]
*/

[Atas]

Kelas concurrent_unordered_multiset

Kelas konkurensi::concurrent_unordered_multiset sangat mirip dengan kelas concurrent_unordered_set kecuali bahwa kelas ini memungkinkan adanya nilai duplikat. Ini juga berbeda dari concurrent_unordered_set dengan cara-cara berikut:

  • Metode concurrent_unordered_multiset::insert mengembalikan iterator alih-alih std::pair<iterator, bool>.

  • Kelas concurrent_unordered_multiset tidak menyediakan operator[] atau at metode .

Contoh berikut menunjukkan struktur dasar untuk menggunakan concurrent_unordered_multiset. Contoh ini menyisipkan nilai karakter dalam rentang ['a', 'i']. concurrent_unordered_multiset memungkinkan nilai terjadi beberapa kali.

// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the set in parallel.

    concurrent_unordered_multiset<char> set; 

    parallel_for(0, 40, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [e] [e] [e] [i] [i] [i] [i] [a] [a] [a] [a] [a] [c] [c] [c] [c] [c] [g] [g]
    [g] [g] [f] [f] [f] [f] [b] [b] [b] [b] [b] [d] [d] [d] [d] [d] [h] [h] [h] [h]
*/

[Atas]

Kelas yang dapat dikombinasikan

Kelas konkurensi::combinable menyediakan penyimpanan lokal utas yang dapat digunakan kembali yang memungkinkan Anda melakukan komputasi terperinci dan kemudian menggabungkan komputasi tersebut ke dalam hasil akhir. Anda dapat menganggap combinable objek sebagai variabel pengurangan.

Kelas combinable ini berguna ketika Anda memiliki sumber daya yang dibagikan di antara beberapa utas atau tugas. Kelas ini combinable membantu Anda menghilangkan status bersama dengan menyediakan akses ke sumber daya bersama dengan cara bebas kunci. Oleh karena itu, kelas ini menyediakan alternatif untuk menggunakan mekanisme sinkronisasi, misalnya, mutex, untuk menyinkronkan akses ke data bersama dari beberapa utas.

Metode dan Fitur

Tabel berikut menunjukkan beberapa metode penting kelas combinable . Untuk informasi selengkapnya tentang semua combinable metode kelas, lihat Kelas yang dapat dikombinasikan.

Metode Deskripsi
lokal Mengambil referensi ke variabel lokal yang terkait dengan konteks utas saat ini.
bersihkan Menghapus semua variabel thread-local dari combinable objek.
Menggabungkan

gabungkan_setiap
Menggunakan fungsi gabungan yang disediakan untuk menghasilkan nilai akhir dari kumpulan semua komputasi thread-local.

Kelas combinable adalah kelas templat yang diparameterkan pada hasil gabungan akhir. Jika Anda memanggil konstruktor default, tipe parameter templat T harus memiliki konstruktor default dan konstruktor salin. T Jika jenis parameter templat tidak memiliki konstruktor default, panggil versi konstruktor yang kelebihan beban yang mengambil fungsi inisialisasi sebagai parameternya.

Anda dapat menyimpan data tambahan dalam combinable objek setelah memanggil metode gabungkanatau combine_each. Anda juga dapat memanggil metode combine dan metode combine_each beberapa kali. Jika tidak ada nilai lokal dalam objek yang combinable berubah, combine metode dan combine_each menghasilkan hasil yang sama setiap kali dipanggil.

Contoh

Untuk contoh tentang cara menggunakan combinable kelas , lihat topik berikut:

[Atas]

Cara: Menggunakan Kontainer Paralel untuk Meningkatkan Efisiensi
Memperlihatkan cara menggunakan kontainer paralel untuk menyimpan dan mengakses data secara efisien secara paralel.

Cara: Menggunakan yang dapat dikombinasikan untuk Meningkatkan Performa
Menunjukkan cara menggunakan combinable kelas untuk menghilangkan status bersama, dan dengan demikian meningkatkan performa.

Cara: Menggunakan yang dapat dikombinasikan untuk Menggabungkan Set
Memperlihatkan cara menggunakan combine fungsi untuk menggabungkan kumpulan data thread-local.

Pustaka Pola Paralel (PPL)
Menjelaskan PPL, yang menyediakan model pemrograman imperatif yang mempromosikan skalabilitas dan kemudahan penggunaan untuk mengembangkan aplikasi bersamaan.

Referensi

Kelas concurrent_vector

Kelas Concurrent_queue

Kelas concurrent_unordered_map

Kelas concurrent_unordered_multimap

Kelas concurrent_unordered_set

Kelas concurrent_unordered_multiset

Kelas yang dapat dikombinasikan