Bagikan melalui


Kontainer dan Objek Paralel

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

Kontainer bersamaan menyediakan akses aman konkurensi ke operasi yang paling penting. Di sini, konkurensi-aman berarti pointer atau iterator selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Fungsionalitas kontainer ini menyerupan 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 bersamaan dibagikan 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 jenis objek bersamaan. 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 ini concurrent_vector memungkinkan operasi penambalan dan akses elemen yang aman konkurensi. Operasi penambah tidak membatalkan penunjuk atau iterator yang ada. Akses iterator dan operasi traversal juga aman konkurensi. Di sini, konkurensi-aman berarti pointer atau iterator selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu.

Perbedaan Antara concurrent_vector dan vektor

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

  • Menambahkan, akses elemen, akses iterator, dan operasi traversal iterator pada concurrent_vector objek aman 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 . vectorSeperti halnya , gunakan metode yang jelas untuk menghapus semua elemen dari concurrent_vector objek.

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

  • Kelas concurrent_vector menentukan 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 Brankas Konkurensi

Semua metode yang menambahkan atau meningkatkan ukuran concurrent_vector objek, atau mengakses elemen dalam concurrent_vector objek, aman konkurensi. Di sini, konkurensi-aman berarti pointer atau iterator 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 konkurensi. 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;
      }
   );
}

end Meskipun metode ini aman konkurensi, panggilan bersamaan ke metode push_back menyebabkan nilai yang dikembalikan oleh end untuk 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, ada kemungkinan kondisi balapan ada di antara push_back dan end panggilan. Metode end ini dapat mengembalikan elemen yang dialokasikan, tetapi tidak sepenuhnya diinisialisasi.

Pengecualian Brankas ty

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 objek, bahkan jika objek dalam keadaan tidak valid.

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

  • 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, konkurensi-aman berarti pointer atau iterator selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu. Kelas ini concurrent_queue juga menyediakan dukungan iterator yang tidak aman konkurensi.

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 size metode . Metode unsafe_size ini tidak aman konkurensi.

Operasi Brankas Konkurensi

Semua metode yang mengantre ke atau menghapus antrean dari concurrent_queue objek aman konkurensi. Di sini, konkurensi-aman berarti pointer atau iterator selalu valid. Ini bukan jaminan inisialisasi elemen, atau urutan traversal tertentu.

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

empty Meskipun metode ini aman konkurensi, operasi bersamaan dapat menyebabkan antrean tumbuh atau menyusut sebelum empty metode kembali.

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

Dukungan Iterator

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

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

Operator 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 konkurensi::concurrent_unordered_map adalah kelas kontainer asosiatif yang, sama seperti kelas std::unordered_map , mengontrol urutan elemen tipe std::p air<const Key yang bervariasi, Ty>. 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 peta dan mengurangi operasi secara paralel, lihat Cara: Melakukan Operasi Peta dan Kurangi 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 keamanan konkurensi, lihat Operasi konkurensi-Brankas.

  • 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 perulangan penerusan.

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

Untuk membantu menghindari kebuntuan concurrent_unordered_map , tidak ada metode 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 Brankas Konkurensi

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 konkurensi. Di sini, konkurensi-aman berarti pointer atau iterator 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 kelas kecuali memungkinkan beberapa nilai untuk memetakan 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 mengaktifkan kunci untuk 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 sangat menyerupai concurrent_unordered_map kelas kecuali mengelola nilai 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']. Aman untuk melakukan penyisipan secara paralel.

// 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 menyerupai concurrent_unordered_set kelas kecuali memungkinkan 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.
hapus Menghapus semua variabel thread-local dari combinable objek.
Menggabungkan

combine_each
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, T jenis parameter templat harus memiliki konstruktor default dan konstruktor salinan. 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 combine metode dan 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.

Parallel Patterns Library (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