Cara: Menggunakan Kelas Konteks untuk Menerapkan Koperasi Semaphore

Topik ini menunjukkan cara menggunakan kelas konkurensi::Konteks untuk mengimplementasikan kelas semaphore kooperatif.

Keterangan

Kelas ini Context memungkinkan Anda memblokir atau menghasilkan konteks eksekusi saat ini. Memblokir atau menghasilkan konteks saat ini berguna ketika konteks saat ini tidak dapat dilanjutkan karena sumber daya tidak tersedia. Semaphore adalah contoh dari satu situasi di mana konteks eksekusi saat ini harus menunggu sumber daya tersedia. Semaphore, seperti objek bagian penting, adalah objek sinkronisasi yang memungkinkan kode dalam satu konteks memiliki akses eksklusif ke sumber daya. Namun, tidak seperti objek bagian penting, semaphore memungkinkan lebih dari satu konteks untuk mengakses sumber daya secara bersamaan. Jika jumlah maksimum konteks menyimpan kunci semaphore, setiap konteks tambahan harus menunggu konteks lain untuk melepaskan kunci.

Untuk mengimplementasikan kelas semaphore

  1. Deklarasikan kelas yang bernama semaphore. Tambahkan public bagian dan private ke kelas ini.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
  1. private Di bagian semaphore kelas, nyatakan variabel std::atomik yang menyimpan jumlah semaphore dan objek konkurensi::concurrent_queue yang menyimpan konteks yang harus menunggu untuk memperoleh semaphore.
// The semaphore count.
atomic<long long> _semaphore_count;

// A concurrency-safe queue of contexts that must wait to 
// acquire the semaphore.
concurrent_queue<Context*> _waiting_contexts;
  1. Di bagian publicsemaphore kelas, terapkan konstruktor. Konstruktor mengambil long long nilai yang menentukan jumlah maksimum konteks yang dapat menyimpan kunci secara bersamaan.
explicit semaphore(long long capacity)
   : _semaphore_count(capacity)
{
}
  1. Di bagian publicsemaphore kelas, terapkan acquire metode . Metode ini mengurangi jumlah semaphore sebagai operasi atom. Jika jumlah semaphore menjadi negatif, tambahkan konteks saat ini ke akhir antrean tunggu dan panggil metode konkurensi::Context::Block untuk memblokir konteks saat ini.
// Acquires access to the semaphore.
void acquire()
{
   // The capacity of the semaphore is exceeded when the semaphore count 
   // falls below zero. When this happens, add the current context to the 
   // back of the wait queue and block the current context.
   if (--_semaphore_count < 0)
   {
      _waiting_contexts.push(Context::CurrentContext());
      Context::Block();
   }
}
  1. Di bagian publicsemaphore kelas, terapkan release metode . Metode ini menaikkan jumlah semaphore sebagai operasi atom. Jika jumlah semaphore negatif sebelum operasi kenaikan, setidaknya ada satu konteks yang menunggu kunci. Dalam hal ini, buka blokir konteks yang ada di bagian depan antrean tunggu.
// Releases access to the semaphore.
void release()
{
   // If the semaphore count is negative, unblock the first waiting context.
   if (++_semaphore_count <= 0)
   {
      // A call to acquire might have decremented the counter, but has not
      // yet finished adding the context to the queue. 
      // Create a spin loop that waits for the context to become available.
      Context* waiting = NULL;
      while (!_waiting_contexts.try_pop(waiting))
      {
         Context::Yield();
      }

      // Unblock the context.
      waiting->Unblock();
   }
}

Contoh

Kelas semaphore dalam contoh ini berperilaku kooperatif karena Context::Block metode dan Context::Yield menghasilkan eksekusi sehingga runtime dapat melakukan tugas lain.

Metode acquire ini mengurangi penghitung, tetapi mungkin tidak selesai menambahkan konteks ke antrean tunggu sebelum konteks lain memanggil release metode . Untuk memperhitungkan hal ini, release metode ini menggunakan perulangan spin yang memanggil metode konkurensi::Context::Yield untuk acquire menunggu metode selesai menambahkan konteks.

Metode release ini dapat memanggil Context::Unblock metode sebelum acquire metode memanggil Context::Block metode . Anda tidak perlu melindungi dari kondisi balapan ini karena runtime memungkinkan metode ini dipanggil dalam urutan apa pun. release Jika metode memanggil Context::Unblock sebelum acquire metode memanggil Context::Block konteks yang sama, konteks tersebut tetap tidak diblokir. Runtime hanya mengharuskan setiap panggilan ke Context::Block dicocokkan dengan panggilan yang sesuai ke Context::Unblock.

Contoh berikut menunjukkan kelas lengkap semaphore . Fungsi ini wmain menunjukkan penggunaan dasar kelas ini. Fungsi ini wmain menggunakan algoritma concurrency::p arallel_for untuk membuat beberapa tugas yang memerlukan akses ke semaphore. Karena tiga utas dapat menahan kunci kapan saja, beberapa tugas harus menunggu tugas lain selesai dan melepaskan kunci.

// cooperative-semaphore.cpp
// compile with: /EHsc
#include <atomic>
#include <concrt.h>
#include <ppl.h>
#include <concurrent_queue.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
   explicit semaphore(long long capacity)
      : _semaphore_count(capacity)
   {
   }

   // Acquires access to the semaphore.
   void acquire()
   {
      // The capacity of the semaphore is exceeded when the semaphore count 
      // falls below zero. When this happens, add the current context to the 
      // back of the wait queue and block the current context.
      if (--_semaphore_count < 0)
      {
         _waiting_contexts.push(Context::CurrentContext());
         Context::Block();
      }
   }

   // Releases access to the semaphore.
   void release()
   {
      // If the semaphore count is negative, unblock the first waiting context.
      if (++_semaphore_count <= 0)
      {
         // A call to acquire might have decremented the counter, but has not
         // yet finished adding the context to the queue. 
         // Create a spin loop that waits for the context to become available.
         Context* waiting = NULL;
         while (!_waiting_contexts.try_pop(waiting))
         {
            Context::Yield();
         }

         // Unblock the context.
         waiting->Unblock();
      }
   }

private:
   // The semaphore count.
   atomic<long long> _semaphore_count;

   // A concurrency-safe queue of contexts that must wait to 
   // acquire the semaphore.
   concurrent_queue<Context*> _waiting_contexts;
};

int wmain()
{
   // Create a semaphore that allows at most three threads to 
   // hold the lock.
   semaphore s(3);

   parallel_for(0, 10, [&](int i) {
      // Acquire the lock.
      s.acquire();

      // Print a message to the console.
      wstringstream ss;
      ss << L"In loop iteration " << i << L"..." << endl;
      wcout << ss.str();

      // Simulate work by waiting for two seconds.
      wait(2000);

      // Release the lock.
      s.release();
   });
}

Contoh ini menghasilkan output sampel berikut.

In loop iteration 5...
In loop iteration 0...
In loop iteration 6...
In loop iteration 1...
In loop iteration 2...
In loop iteration 7...
In loop iteration 3...
In loop iteration 8...
In loop iteration 9...
In loop iteration 4...

Untuk informasi selengkapnya tentang concurrent_queue kelas, lihat Kontainer dan Objek Paralel. Untuk informasi selengkapnya tentang parallel_for algoritma, lihat Algoritma Paralel.

Mengompilasi Kode

Salin kode contoh dan tempelkan dalam proyek Visual Studio, atau tempelkan dalam file yang diberi nama cooperative-semaphore.cpp lalu jalankan perintah berikut di jendela Prompt Perintah Visual Studio.

cl.exe /EHsc cooperative-semaphore.cpp

Pemrograman yang Kuat

Anda dapat menggunakan pola Resource Acquisition Is Initialization (RAII) untuk membatasi akses ke semaphore objek ke cakupan tertentu. Di bawah pola RAII, struktur data dialokasikan pada tumpukan. Struktur data tersebut menginisialisasi atau memperoleh sumber daya saat dibuat dan menghancurkan atau merilis sumber daya tersebut saat struktur data dihancurkan. Pola RAII menjamin bahwa destruktor dipanggil sebelum cakupan penutup keluar. Oleh karena itu, sumber daya dikelola dengan benar ketika pengecualian dilemparkan atau ketika fungsi berisi beberapa return pernyataan.

Contoh berikut mendefinisikan kelas yang diberi nama scoped_lock, yang didefinisikan di bagian semaphorepublic kelas. Kelas scoped_lock menyerupan kelas konkurensi::critical_section::scoped_lock dan konkurensi::reader_writer_lock::scoped_lock . Konstruktor semaphore::scoped_lock kelas memperoleh akses ke objek tertentu semaphore dan destruktor merilis akses ke objek tersebut.

// An exception-safe RAII wrapper for the semaphore class.
class scoped_lock
{
public:
   // Acquires access to the semaphore.
   scoped_lock(semaphore& s)
      : _s(s)
   {
      _s.acquire();
   }
   // Releases access to the semaphore.
   ~scoped_lock()
   {
      _s.release();
   }

private:
   semaphore& _s;
};

Contoh berikut memodifikasi isi fungsi kerja yang diteruskan ke parallel_for algoritma sehingga menggunakan RAII untuk memastikan bahwa semaphore dirilis sebelum fungsi kembali. Teknik ini memastikan bahwa fungsi kerja aman pengecualian.

parallel_for(0, 10, [&](int i) {
   // Create an exception-safe scoped_lock object that holds the lock 
   // for the duration of the current scope.
   semaphore::scoped_lock auto_lock(s);

   // Print a message to the console.
   wstringstream ss;
   ss << L"In loop iteration " << i << L"..." << endl;
   wcout << ss.str();

   // Simulate work by waiting for two seconds.
   wait(2000);
});

Baca juga

Konteks
Kontainer dan Objek Paralel