Bagikan melalui


Masalah Sinkronisasi dan Multiproscessor

Aplikasi mungkin mengalami masalah saat dijalankan pada sistem multiprosesor karena asumsi yang mereka buat yang hanya valid pada sistem prosesor tunggal.

Prioritas Utas

Pertimbangkan program dengan dua utas, satu dengan prioritas lebih tinggi daripada yang lain. Pada sistem prosesor tunggal, utas prioritas yang lebih tinggi tidak akan melepaskan kontrol ke utas prioritas yang lebih rendah karena penjadwal memberikan preferensi ke utas prioritas yang lebih tinggi. Pada sistem multiprosesor, kedua utas dapat berjalan secara bersamaan, masing-masing pada prosesornya sendiri.

Aplikasi harus menyinkronkan akses ke struktur data untuk menghindari kondisi balapan. Kode yang mengasumsikan bahwa utas prioritas yang lebih tinggi berjalan tanpa gangguan dari utas prioritas yang lebih rendah akan gagal pada sistem multiprosedur.

Pengurutan Memori

Saat prosesor menulis ke lokasi memori, nilai di-cache untuk meningkatkan performa. Demikian pula, prosesor mencoba memenuhi permintaan baca dari cache untuk meningkatkan performa. Selain itu, prosesor mulai mengambil nilai dari memori sebelum diminta oleh aplikasi. Ini dapat terjadi sebagai bagian dari eksekusi spekulatif atau karena masalah baris cache.

Cache CPU dapat dipartisi ke bank yang dapat diakses secara paralel. Ini berarti bahwa operasi memori dapat diselesaikan secara tidak berurutan. Untuk memastikan bahwa operasi memori selesai secara berurutan, sebagian besar prosesor memberikan instruksi penghubung memori. Hambatan memori penuh memastikan bahwa operasi baca dan tulis memori yang muncul sebelum instruksi penghalang memori diterapkan ke memori sebelum operasi baca dan tulis memori yang muncul setelah instruksi penghalang memori. Penghalang memori baca hanya mengurutkan operasi baca memori dan penghalang memori tulis hanya mengurutkan operasi penulisan memori. Instruksi ini juga memastikan bahwa pengkompilasi menonaktifkan pengoptimalan apa pun yang dapat menyusun ulang operasi memori di seluruh penghalang.

Prosesor dapat mendukung instruksi untuk penghalang memori dengan semantik perolehan, pelepasan, dan pagar. Semantik ini menjelaskan urutan di mana hasil operasi tersedia. Dengan memperoleh semantik, hasil operasi tersedia sebelum hasil operasi apa pun yang muncul setelah dalam kode. Dengan semantik rilis, hasil operasi tersedia setelah hasil operasi apa pun yang muncul sebelum dalam kode. Semantik pagar menggabungkan semantik akuisisi dan pelepasan. Hasil operasi dengan semantik pagar tersedia sebelum operasi apa pun yang muncul setelahnya dalam kode dan setelah operasi apa pun yang muncul sebelumnya.

Pada prosesor x86 dan x64 yang mendukung SSE2, instruksinya adalah mfence (pagar memori), lfence (load fence), dan sfence (store fence). Pada prosesor ARM, gangguannya adalah dmb dan dsb. Untuk informasi selengkapnya, lihat dokumentasi untuk prosesor.

Fungsi sinkronisasi berikut menggunakan penghalang yang sesuai untuk memastikan pengurutan memori:

  • Fungsi yang memasukkan atau meninggalkan bagian penting
  • Fungsi yang memperoleh atau melepaskan kunci SRW
  • Inisialisasi satu kali dimulai dan selesai
  • Fungsi EnterSynchronizationBarrier
  • Fungsi yang menandakan objek sinkronisasi
  • Fungsi tunggu
  • Fungsi yang saling di-interlock (kecuali fungsi dengan akhiran NoFence , atau intrinsik dengan akhiran _nf )

Memperbaiki Kondisi Balapan

Kode berikut memiliki kondisi balapan pada sistem multiprosesor karena prosesor yang menjalankan CacheComputedValue pertama kali dapat menulis fValueHasBeenComputed ke memori utama sebelum menulis iValue ke memori utama. Akibatnya, prosesor kedua yang dijalankan FetchComputedValue pada saat yang sama berbunyi fValueHasBeenComputed sebagai TRUE, tetapi nilai iValue baru masih dalam cache prosesor pertama dan belum ditulis ke memori.

int iValue;
BOOL fValueHasBeenComputed = FALSE;
extern int ComputeValue();

void CacheComputedValue()
{
  if (!fValueHasBeenComputed) 
  {
    iValue = ComputeValue();
    fValueHasBeenComputed = TRUE;
  }
}
 
BOOL FetchComputedValue(int *piResult)
{
  if (fValueHasBeenComputed) 
  {
    *piResult = iValue;
    return TRUE;
  } 

  else return FALSE;
}

Kondisi balapan di atas dapat diperbaiki dengan menggunakan kata kunci volatil atau fungsi InterlockedExchange untuk memastikan bahwa nilai iValue diperbarui untuk semua prosesor sebelum nilai fValueHasBeenComputed diatur ke TRUE.

Dimulai dengan Visual Studio 2005, jika dikompilasi dalam mode /volatile:ms , kompilator menggunakan memperoleh semantik untuk operasi baca pada variabel volatil dan merilis semantik untuk operasi tulis pada variabel volatil (ketika didukung oleh CPU). Oleh karena itu, Anda dapat memperbaiki contoh sebagai berikut:

volatile int iValue;
volatile BOOL fValueHasBeenComputed = FALSE;
extern int ComputeValue();

void CacheComputedValue()
{
  if (!fValueHasBeenComputed) 
  {
    iValue = ComputeValue();
    fValueHasBeenComputed = TRUE;
  }
}
 
BOOL FetchComputedValue(int *piResult)
{
  if (fValueHasBeenComputed) 
  {
    *piResult = iValue;
    return TRUE;
  } 

  else return FALSE;
}

Dengan Visual Studio 2003, referensi volatil terhadap volatil diurutkan; pengkompilasi tidak akan memesan ulang akses variabel volatil. Namun, operasi ini dapat dipesan ulang oleh prosesor. Oleh karena itu, Anda dapat memperbaiki contoh sebagai berikut:

int iValue;
BOOL fValueHasBeenComputed = FALSE;
extern int ComputeValue();

void CacheComputedValue()
{
  if (InterlockedCompareExchange((LONG*)&fValueHasBeenComputed, 
          FALSE, FALSE)==FALSE) 
  {
    InterlockedExchange ((LONG*)&iValue, (LONG)ComputeValue());
    InterlockedExchange ((LONG*)&fValueHasBeenComputed, TRUE);
  }
}
 
BOOL FetchComputedValue(int *piResult)
{
  if (InterlockedCompareExchange((LONG*)&fValueHasBeenComputed, 
          TRUE, TRUE)==TRUE) 
  {
    InterlockedExchange((LONG*)piResult, (LONG)iValue);
    return TRUE;
  } 

  else return FALSE;
}

Objek Bagian Penting

Akses Variabel Yang Saling Diblokir

Fungsi Tunggu