Pertanyaan Tukar bebas kunci dari dua unique_ptr


Bertukar dua unique_ptrs tidak dijamin menjadi threadsafe.

std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe

Karena saya perlu menukar pointer atom dan karena saya suka penanganan kepemilikan unique_ptr, apakah ada cara sederhana untuk menggabungkan keduanya?


Edit: Jika ini tidak mungkin, saya terbuka untuk alternatif. Saya setidaknya ingin melakukan sesuatu seperti ini:

threadshared_unique_ptr<T> global;

void f() {
   threadlocal_unique_ptr<T> local(new T(...));
   local.swap_content(global); // atomically for global
}

Apa cara idiomatik untuk melakukan ini di C ++ 11?


32
2018-03-17 12:43


asal


Jawaban:


Mengunci bebas dua pointer

Tampaknya tidak ada solusi bebas kunci umum untuk masalah ini. Untuk melakukan ini, Anda perlu kemungkinan untuk menulis nilai-nilai baru ke dalam dua lokasi memori yang tidak berkelanjutan. Ini disebut DCAS, tetapi tidak tersedia di prosesor Intel.

Pengalihan kepemilikan bebas-lock

Yang satu ini mungkin, karena hanya diperlukan untuk menyimpan nilai baru ke dalam atom global dan menerima nilainya yang lama. Ide pertamaku adalah menggunakan CAS operasi. Lihatlah kode berikut untuk mendapatkan ide:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = nullptr;
   do {
       temp = global;                                                   // 1
   } while(!std::atomic_compare_exchange_weak(&global, &temp, local));  // 2

   delete temp;
}

Tangga

  1. Ingat saat ini global penunjuk temp
  2. Menyimpan local untuk global jika global masih sama dengan temp (itu tidak diubah oleh utas lainnya). Coba lagi jika ini tidak benar.

Sebenarnya, CAS terlalu berlebihan di sana, karena kami tidak melakukan sesuatu yang istimewa dengan yang lama global nilai sebelum diubah. Jadi, kita hanya bisa menggunakan operasi pertukaran atom:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = std::atomic_exchange(&global, local);
   delete temp;
}

Lihat Jonathan menjawab untuk solusi yang lebih pendek dan elegan.

Bagaimanapun, Anda harus menulis penunjuk pintar Anda sendiri. Anda tidak dapat menggunakan trik ini dengan standar unique_ptr.


16
2018-03-17 19:02



Cara idiomatis untuk memodifikasi dua variabel secara atomis adalah dengan menggunakan kunci.

Anda tidak bisa melakukannya std::unique_ptr tanpa kunci. Bahkan std::atomic<int> tidak menyediakan cara untuk menukar dua nilai secara atom. Anda dapat memperbarui satu atom dan mendapatkan kembali nilai sebelumnya, tetapi swap secara konseptual tiga langkah, dalam hal std::atomic API mereka:

auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);

Ini adalah atom Baca baca diikuti oleh atom baca-ubah-tulis diikuti oleh atom menulis. Setiap langkah dapat dilakukan secara atomik, tetapi Anda tidak dapat melakukan ketiga atom secara keseluruhan tanpa kunci.

Untuk nilai yang tidak dapat dikreditkan seperti std::unique_ptr<T> Anda bahkan tidak bisa menggunakan load dan store operasi di atas, tetapi harus dilakukan:

auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);

Ini tiga baca-ubah-tulis operasi. (Anda tidak bisa menggunakannya std::atomic<std::unique_ptr<T>> untuk melakukan itu, karena memerlukan jenis argumen yang dapat dipecahkan secara sepele, dan std::unique_ptr<T> bukan jenis yang bisa ditiru.)

Untuk melakukannya dengan lebih sedikit operasi akan membutuhkan API berbeda yang tidak didukung oleh std::atomic karena tidak dapat diimplementasikan karena seperti yang dikatakan Stas, tidak mungkin dengan kebanyakan prosesor. Standar C ++ tidak dalam kebiasaan fungsi standardisasi yang tidak mungkin pada semua arsitektur kontemporer. (Tidak dengan sengaja pula!)

Edit: Pertanyaan Anda yang diperbarui bertanya tentang masalah yang sangat berbeda, dalam contoh kedua Anda tidak perlu swap atom yang mempengaruhi dua objek. Hanya global dibagi di antara utas, jadi Anda tidak peduli jika pembaruan localbersifat atom, Anda hanya perlu memperbarui secara atom global dan mengambil nilai lama. The canonical C ++ 11 cara untuk melakukannya adalah dengan std:atomic<T*> dan Anda bahkan tidak perlu variabel kedua:

atomic<T*> global;

void f() {
   delete global.exchange(new T(...));
}

Ini adalah single baca-ubah-tulis operasi.


17
2018-03-17 19:14