Apa perbedaan antara mutex dan bagian kritis?

134

Tolong jelaskan dari Linux, perspektif Windows?

Saya pemrograman dalam C #, apakah kedua istilah ini membuat perbedaan. Silakan memposting sebanyak mungkin, dengan contoh dan ....

Terima kasih


sumber

Jawaban:

232

Untuk Windows, bagian-bagian penting lebih ringan dari pada mutex.

Mutex dapat dibagi antara proses, tetapi selalu menghasilkan panggilan sistem ke kernel yang memiliki beberapa overhead.

Bagian kritis hanya dapat digunakan dalam satu proses, tetapi memiliki keuntungan bahwa mereka hanya beralih ke mode kernel dalam kasus pertengkaran - Pengambilan yang tidak terkendali, yang seharusnya merupakan kasus umum, sangat cepat. Dalam kasus pertengkaran, mereka memasuki kernel untuk menunggu sinkronisasi primitif (seperti acara atau semaphore).

Saya menulis contoh aplikasi cepat yang membandingkan waktu antara keduanya. Pada sistem saya untuk 1.000.000 akuisisi dan rilis yang tidak dibiarkan, mutex membutuhkan waktu lebih dari satu detik. Bagian kritis membutuhkan ~ 50 ms untuk 1.000.000 akuisisi.

Inilah kode tes, saya menjalankan ini dan mendapatkan hasil yang serupa jika mutex pertama atau kedua, jadi kami tidak melihat efek lainnya.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);
Michael
sumber
1
Tidak yakin apakah ini berhubungan atau tidak (karena saya belum mengkompilasi dan mencoba kode Anda), tetapi saya telah menemukan bahwa memanggil WaitForSingleObject dengan INFINITE menghasilkan kinerja yang buruk. Melewatinya nilai batas waktu 1 kemudian perulangan sambil memeriksa kembali itu telah membuat perbedaan besar dalam kinerja beberapa kode saya. Ini sebagian besar dalam konteks menunggu pegangan proses eksternal, namun ... Bukan mutex. YMMV. Saya tertarik melihat bagaimana mutex bekerja dengan modifikasi itu. Perbedaan waktu yang dihasilkan dari tes ini tampaknya lebih besar dari yang diharapkan.
Troy Howard
5
@ TraHoward bukankah pada dasarnya Anda hanya berputar mengunci pada saat itu?
dss539
Alasan untuk perbedaan ini kemungkinan besar terutama historis. Tidak sulit untuk menerapkan penguncian secepat CriticalSection dalam case yang tidak dikontrol (beberapa instruksi atom, tanpa syscalls), namun bekerja lintas proses (dengan memori bersama). Lihat misal Linux futex .
Regnarg
2
@TroyHoward mencoba memaksa CPU Anda berjalan pada 100% setiap saat dan lihat apakah INFINITE berfungsi lebih baik. Strategi daya dapat memakan waktu selama 40 ms pada mesin saya (Dell XPS-8700) untuk merangkak kembali ke kecepatan penuh setelah memutuskan untuk memperlambat, yang mungkin tidak dilakukan jika Anda tidur atau menunggu hanya satu milidetik.
Stevens Miller
Saya tidak yakin saya mengerti apa yang ditunjukkan di sini. Secara umum, memasuki bagian kritis membutuhkan beberapa jenis semaphore. Apakah Anda mengatakan bahwa di balik layar, O / S memiliki cara yang efisien untuk menerapkan perilaku bagian kritis ini tanpa memerlukan mutex?
SN
89

Dari perspektif teoretis, bagian kritis adalah sepotong kode yang tidak boleh dijalankan oleh banyak utas sekaligus karena kode mengakses sumber daya bersama.

Sebuah mutex adalah sebuah algoritma (dan kadang-kadang nama struktur data) yang digunakan untuk melindungi bagian penting.

Semaphores dan Monitor adalah implementasi umum dari mutex.

Dalam praktiknya ada banyak implementasi mutex yang tersedia di windows. Mereka terutama berbeda sebagai konsekuensi dari implementasi mereka oleh tingkat penguncian mereka, cakupan mereka, biaya mereka, dan kinerja mereka di bawah berbagai tingkat pertikaian. Lihat CLR Inside Out - Menggunakan concurrency untuk skalabilitas untuk bagan biaya implementasi mutex yang berbeda.

Primitif sinkronisasi yang tersedia.

The lock(object)pernyataan dilaksanakan menggunakan Monitor- lihat MSDN untuk referensi.

Dalam beberapa tahun terakhir banyak penelitian yang dilakukan pada sinkronisasi non-blocking . Tujuannya adalah untuk mengimplementasikan algoritme dengan cara bebas kunci atau bebas menunggu. Dalam algoritma seperti itu suatu proses membantu proses lain untuk menyelesaikan pekerjaan mereka sehingga proses akhirnya dapat menyelesaikan pekerjaannya. Karena itu suatu proses dapat menyelesaikan pekerjaannya bahkan ketika proses lain, yang mencoba melakukan beberapa pekerjaan, hang. Gunakan kunci, mereka tidak akan melepaskan kunci mereka dan mencegah proses lainnya dari melanjutkan.

Daniel Brückner
sumber
Melihat jawaban yang diterima, saya berpikir mungkin saya salah mengingat konsep bagian kritis, sampai saya melihat bahwa Perspektif Teoretis yang Anda tulis. :)
Anirudh Ramanathan
2
Praktis kunci pemrograman gratis seperti Shangri La, kecuali itu ada. Makalah Keir Fraser (PDF) mengeksplorasi ini agak menarik (kembali ke 2004). Dan kami masih berjuang dengan itu pada tahun 2012. Kami payah.
Tim Post
22

Selain jawaban lain, detail berikut khusus untuk bagian penting di windows:

  • dengan tidak adanya pertengkaran, memperoleh bagian penting adalah sesederhana InterlockedCompareExchangeoperasi
  • struktur bagian kritis menampung ruang untuk mutex. Awalnya tidak terisi
  • jika ada pertentangan antara utas untuk bagian kritis, mutex akan dialokasikan dan digunakan. Kinerja bagian kritis akan menurun ke mutex
  • jika Anda mengantisipasi pertengkaran tinggi, Anda dapat mengalokasikan bagian kritis menentukan jumlah putaran.
  • jika ada pertentangan pada bagian kritis dengan jumlah putaran, utas yang berusaha mendapatkan bagian kritis akan berputar (sibuk-tunggu) untuk banyak siklus prosesor. Hal ini dapat menghasilkan kinerja yang lebih baik daripada tidur, karena jumlah siklus untuk melakukan pergantian konteks ke utas lain dapat jauh lebih tinggi daripada jumlah siklus yang diambil oleh utas pemilik untuk melepaskan mutex
  • jika jumlah putaran kedaluwarsa, mutex akan dialokasikan
  • ketika utas yang dimiliki melepaskan bagian kritis, diperlukan untuk memeriksa apakah mutex dialokasikan, jika itu maka akan mengatur mutex untuk melepaskan utas menunggu

Di linux, saya pikir mereka memiliki "kunci putaran" yang melayani tujuan yang mirip dengan bagian kritis dengan jumlah putaran.

INFORMASI 1800
sumber
Sayangnya bagian kritis Window melibatkan melakukan operasi CAS dalam mode kernel , yang secara besar-besaran lebih mahal daripada operasi yang saling bertautan. Juga, bagian kritis Windows dapat memiliki jumlah putaran yang terkait dengannya.
Promit
2
Jelas itu tidak benar. CAS dapat dilakukan dengan cmpxchg dalam mode pengguna.
Michael
Saya pikir jumlah putaran default adalah nol jika Anda memanggil InitializeCriticalSection - Anda harus memanggil InitializeCriticalSectionAndSpinCount jika Anda ingin penghitungan putaran diterapkan. Apakah Anda punya referensi untuk itu?
1800 INFORMASI
18

Critical Section dan Mutex tidak spesifik sistem operasi, konsep multithreading / multiprocessing.

Bagian Kritis Adalah sepotong kode yang hanya boleh dijalankan sendiri pada waktu tertentu (misalnya, ada 5 utas yang berjalan secara bersamaan dan fungsi yang disebut "critical_section_function" yang memperbarui array ... Anda tidak ingin semua 5 utas memperbarui array sekaligus. Jadi ketika program ini menjalankan critical_section_function (), tidak ada thread lain yang harus menjalankan critical_section_function mereka.

mutex * Mutex adalah cara mengimplementasikan kode bagian kritis (anggap saja seperti token ... utas harus memiliki itu untuk menjalankan critical_section_code)

Yang tidak diketahui
sumber
2
Juga, mutex dapat dibagikan di seluruh proses.
konfigurator
14

Mutex adalah objek yang dapat diperoleh thread, mencegah utas lainnya mendapatkannya. Itu adalah nasihat, bukan wajib; sebuah thread dapat menggunakan sumber daya yang diwakili oleh mutex tanpa mendapatkannya.

Bagian kritis adalah panjang kode yang dijamin oleh sistem operasi untuk tidak diinterupsi. Dalam pseudo-code, itu akan seperti:

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();
Zifre
sumber
1
Saya pikir poster itu berbicara tentang primitif sinkronisasi mode pengguna, seperti objek bagian win32 Kritis, yang hanya memberikan saling pengecualian. Saya tidak tahu tentang Linux, tetapi kernel Windows memiliki wilayah kritis yang berperilaku seperti yang Anda jelaskan - tidak dapat disela.
Michael
1
Saya tidak tahu mengapa Anda terpilih. Ada konsep bagian kritis, yang telah Anda gambarkan dengan benar, yang berbeda dari objek kernel Windows yang disebut CriticalSection, yang merupakan jenis mutex. Saya percaya OP bertanya tentang definisi yang terakhir.
Adam Rosenfield
Setidaknya saya bingung dengan tag agnostik bahasa. Tetapi bagaimanapun juga inilah yang kita dapatkan untuk Microsoft yang menyebutkan implementasi mereka sama dengan kelas dasar mereka. Praktek pengkodean yang buruk!
Mikko Rantanen
Yah, dia meminta sedetail mungkin, dan secara khusus mengatakan Windows dan Linux sehingga terdengar seperti konsep yang baik. +1 - tidak mengerti -1: /
Jason Coco
14

Windows 'fast' yang setara dengan pemilihan kritis di Linux akan menjadi futex , yang merupakan singkatan dari mutex ruang pengguna yang cepat. Perbedaan antara futex dan mutex adalah bahwa dengan futex, kernel hanya menjadi terlibat ketika arbitrasi diperlukan, sehingga Anda menghemat biaya bicara dengan kernel setiap kali penghitung atom diubah. Itu .. dapat menyimpan signifikan jumlah waktu kunci negosiasi dalam beberapa aplikasi.

Futex juga dapat dibagikan di antara proses, menggunakan cara yang Anda gunakan untuk berbagi mutex.

Sayangnya, futex bisa sangat sulit untuk diterapkan (PDF). (Pemutakhiran 2018, mereka hampir tidak menakutkan seperti pada 2009).

Selain itu, hampir sama di kedua platform. Anda membuat pembaruan atomik yang didorong token ke struktur bersama dengan cara yang (mudah-mudahan) tidak menyebabkan kelaparan. Yang tersisa hanyalah metode untuk mencapai itu.

Pos Tim
sumber
6

Di Windows, bagian penting adalah lokal untuk proses Anda. Mutex dapat dibagikan / diakses di seluruh proses. Pada dasarnya, bagian kritis jauh lebih murah. Tidak dapat mengomentari Linux secara khusus, tetapi pada beberapa sistem mereka hanya alias untuk hal yang sama.

Promit
sumber
6

Hanya untuk menambahkan 2 sen saya, Bagian kritis didefinisikan sebagai struktur dan operasi pada mereka dilakukan dalam konteks mode pengguna.

ntdll! _RTL_CRITICAL_SECTION
   + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   + 0x004 LockCount: Int4B
   + 0x008 RecursionCount: Int4B
   + 0x00c OwningThread: Ptr32 Void
   + 0x010 LockSemaphore: Ptr32 Void
   + 0x014 SpinCount: Uint4B

Sedangkan mutex adalah objek kernel (ExMutantObjectType) yang dibuat di direktori objek Windows. Operasi mutex sebagian besar diimplementasikan dalam mode kernel. Misalnya, saat membuat Mutex, Anda akhirnya memanggil nt! NtCreateMutant di kernel.

Martin
sumber
Apa yang terjadi ketika sebuah program yang menginisialisasi dan menggunakan objek Mutex, lumpuh? Apakah objek Mutex akan secara otomatis dialokasikan ulang? Tidak, saya akan katakan. Baik?
Ankur
6
Objek kernel memiliki jumlah referensi. Menutup pegangan ke objek mengurangi jumlah referensi dan ketika mencapai 0 objek dibebaskan. Ketika suatu proses macet, semua pegangannya secara otomatis ditutup, sehingga mutex yang hanya dimiliki oleh proses itu akan secara otomatis dialokasikan kembali.
Michael
Dan ini adalah alasan mengapa objek Bagian Kritis terikat proses, di sisi lain mutex dapat dibagi di seluruh proses.
Sisir
2

Jawaban yang bagus dari Michael. Saya telah menambahkan tes ketiga untuk kelas mutex yang diperkenalkan di C ++ 11. Hasilnya agak menarik, dan masih mendukung dukungan aslinya objek CRITICAL_SECTION untuk proses tunggal.

mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    m.lock();
    m.unlock();
}

QueryPerformanceCounter(&end);

int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);

Hasil saya adalah 217, 473, dan 19 (perhatikan bahwa rasio waktu saya untuk dua terakhir kira-kira sebanding dengan Michael, tetapi mesin saya setidaknya empat tahun lebih muda darinya, sehingga Anda dapat melihat bukti peningkatan kecepatan antara 2009 dan 2013 , ketika XPS-8700 keluar). Kelas mutex baru dua kali lebih cepat dari mutex Windows, tetapi masih kurang dari sepersepuluh kecepatan objek Windows CRITICAL_SECTION. Perhatikan bahwa saya hanya menguji mutex non-rekursif. Objek CRITICAL_SECTION bersifat rekursif (satu utas dapat memasukkannya berulang kali, asalkan meninggalkan jumlah yang sama kali).

Stevens Miller
sumber
0

Fungsi AC disebut reentrant jika hanya menggunakan parameter aktualnya.

Fungsi reentrant dapat dipanggil oleh banyak utas secara bersamaan.

Contoh fungsi reentrant:

int reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   return c;
}

Contoh fungsi non reentrant:

int result;

void non_reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   result = c;

}

Strtok library C standar () tidak reentrant dan tidak dapat digunakan oleh 2 utas atau lebih pada saat yang sama.

Beberapa platform SDK dilengkapi dengan versi reentrant dari strtok () yang disebut strtok_r ();

Enrico Migliore

Enrico Migliore
sumber