กรุณาอธิบายจาก Linux, Windows มุมมอง?
ฉันกำลังเขียนโปรแกรมในภาษา C # คำศัพท์ทั้งสองนี้จะสร้างความแตกต่างได้หรือไม่ กรุณาโพสต์ให้มากที่สุดพร้อมตัวอย่างและเช่น ....
ขอบคุณ
กรุณาอธิบายจาก Linux, Windows มุมมอง?
ฉันกำลังเขียนโปรแกรมในภาษา C # คำศัพท์ทั้งสองนี้จะสร้างความแตกต่างได้หรือไม่ กรุณาโพสต์ให้มากที่สุดพร้อมตัวอย่างและเช่น ....
ขอบคุณ
คำตอบ:
สำหรับ Windows ส่วนที่สำคัญจะมีน้ำหนักเบากว่า mutexes
Mutexes สามารถใช้ร่วมกันระหว่างกระบวนการได้ แต่จะส่งผลให้ระบบเรียกไปยังเคอร์เนลซึ่งมีค่าใช้จ่ายอยู่บ้าง
ส่วนที่สำคัญสามารถใช้ได้ภายในกระบวนการเดียวเท่านั้น แต่มีข้อได้เปรียบที่พวกเขาเปลี่ยนไปใช้โหมดเคอร์เนลในกรณีที่มีการโต้แย้งเท่านั้น - การได้มาโดยไม่ต้องมีการควบคุมซึ่งควรเป็นกรณีทั่วไปนั้นรวดเร็วอย่างไม่น่าเชื่อ ในกรณีของความขัดแย้งพวกเขาเข้าสู่เคอร์เนลเพื่อรอการซิงโครไนซ์แบบดั้งเดิม (เช่นเหตุการณ์หรือสัญญาณ)
ฉันเขียนแอปตัวอย่างสั้น ๆ ที่เปรียบเทียบเวลาระหว่างทั้งสองแอป ในระบบของฉันสำหรับการรับและเผยแพร่ที่ไม่มีการควบคุม 1,000,000 รายการ mutex จะใช้เวลามากกว่าหนึ่งวินาที ส่วนที่สำคัญใช้เวลาประมาณ 50 ms สำหรับการได้มา 1,000,000 ครั้ง
นี่คือรหัสทดสอบฉันรันสิ่งนี้และได้ผลลัพธ์ที่คล้ายกันหาก mutex เป็นครั้งแรกหรือครั้งที่สองดังนั้นเราจึงไม่เห็นเอฟเฟกต์อื่น ๆ
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);
จากมุมมองทางทฤษฎีส่วนที่สำคัญคือส่วนของโค้ดที่ต้องไม่รันโดยหลายเธรดพร้อมกันเนื่องจากโค้ดเข้าถึงทรัพยากรที่ใช้ร่วมกัน
mutexเป็นอัลกอริทึม (และบางครั้งชื่อของโครงสร้างข้อมูล) ที่ใช้ในการป้องกันส่วนที่สำคัญ
SemaphoresและMonitorsเป็นการนำ mutex ไปใช้งานทั่วไป
ในทางปฏิบัติมีการใช้งาน mutex มากมายใน windows ส่วนใหญ่แตกต่างกันเนื่องจากการนำไปใช้งานตามระดับการล็อกขอบเขตค่าใช้จ่ายและประสิทธิภาพภายใต้การช่วงชิงระดับที่แตกต่างกัน ดูCLR Inside Out - การใช้ความพร้อมกันสำหรับความสามารถในการปรับขนาดสำหรับแผนภูมิต้นทุนของการใช้งาน mutex ที่แตกต่างกัน
Primitives การซิงโครไนซ์ที่พร้อมใช้งาน
lock(object)
คำสั่งจะดำเนินการโดยใช้Monitor
- ดูMSDNสำหรับการอ้างอิง
ในปีที่ผ่านมาการวิจัยมากจะทำในไม่ปิดกั้นการประสาน เป้าหมายคือการนำอัลกอริทึมไปใช้ด้วยวิธีที่ไม่ต้องล็อกหรือไม่ต้องรอ ในอัลกอริทึมดังกล่าวกระบวนการจะช่วยให้กระบวนการอื่น ๆ ทำงานให้เสร็จเพื่อให้กระบวนการทำงานเสร็จสิ้นในที่สุด ด้วยเหตุนี้กระบวนการสามารถทำงานให้เสร็จสิ้นได้แม้ในขณะที่กระบวนการอื่น ๆ ที่พยายามทำงานบางอย่างก็หยุดทำงาน Usinig ล็อกพวกเขาจะไม่คลายล็อกและป้องกันไม่ให้กระบวนการอื่น ๆ ดำเนินต่อไป
นอกเหนือจากคำตอบอื่น ๆ แล้วรายละเอียดต่อไปนี้เป็นข้อมูลเฉพาะของส่วนที่สำคัญบน windows:
InterlockedCompareExchange
ดำเนินการในลินุกซ์ฉันคิดว่าพวกเขามี "สปินล็อค" ที่ทำหน้าที่คล้ายกันกับส่วนวิกฤตที่มีการนับสปิน
Critical Section และ Mutex ไม่ใช่ระบบปฏิบัติการที่เฉพาะเจาะจง แต่เป็นแนวคิดของการประมวลผลแบบหลายเธรด / มัลติโปรเซสเซอร์
Critical Section คือโค้ดส่วนหนึ่งที่ต้องรันด้วยตัวเองในช่วงเวลาใดเวลาหนึ่งเท่านั้น (ตัวอย่างเช่นมี 5 เธรดที่ทำงานพร้อมกันและฟังก์ชันที่เรียกว่า "Critical_section_function" ซึ่งจะอัปเดตอาร์เรย์ ... คุณไม่ต้องการให้ทั้ง 5 เธรด การอัปเดตอาร์เรย์ในครั้งเดียวดังนั้นเมื่อโปรแกรมกำลังเรียกใช้ Critical_section_function () เธรดอื่น ๆ จะต้องรันฟังก์ชัน Critical_section_function ไม่ได้
mutex * Mutex เป็นวิธีการใช้งานโค้ดส่วนที่สำคัญ (คิดว่ามันเหมือนโทเค็น ... เธรดจะต้องมีมันอยู่ในครอบครองเพื่อเรียกใช้ Critical_section_code)
mutex เป็นอ็อบเจ็กต์ที่เธรดสามารถรับได้เพื่อป้องกันไม่ให้เธรดอื่นได้รับมัน เป็นที่ปรึกษาไม่บังคับ เธรดสามารถใช้ทรัพยากรที่ mutex แสดงโดยไม่ต้องรับมัน
ส่วนที่สำคัญคือความยาวของรหัสที่ระบบปฏิบัติการรับประกันว่าจะไม่ถูกแทรกแซง ในรหัสหลอกจะเป็นดังนี้:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
Windows ที่ 'เร็ว' เท่ากับการเลือกที่สำคัญใน Linux จะเป็นfutexซึ่งย่อมาจาก fast user space mutex ความแตกต่างระหว่าง futex และ mutex คือเมื่อใช้ futex เคอร์เนลจะมีส่วนเกี่ยวข้องเมื่อจำเป็นต้องใช้อนุญาโตตุลาการเท่านั้นดังนั้นคุณจึงประหยัดค่าใช้จ่ายในการพูดคุยกับเคอร์เนลทุกครั้งที่มีการแก้ไขตัวนับอะตอม ว่า .. สามารถบันทึกอย่างมีนัยสำคัญจำนวนเวลาล็อคการเจรจาต่อรองในบางโปรแกรม
นอกจากนี้ยังสามารถแชร์ futex ระหว่างกระบวนการต่างๆได้โดยใช้วิธีที่คุณจะใช้ในการแบ่งปัน mutex
น่าเสียดายที่ futexes สามารถใช้งานได้ (PDF) เป็นเรื่องยุ่งยากมาก (อัปเดตปี 2018 ไม่น่ากลัวเท่าปี 2009)
นอกเหนือจากนั้นมันก็เหมือนกันในทั้งสองแพลตฟอร์ม คุณกำลังทำการอัปเดตอะตอมที่ขับเคลื่อนด้วยโทเค็นไปยังโครงสร้างที่ใช้ร่วมกันในลักษณะที่ (หวังว่า) จะไม่ทำให้เกิดความอดอยาก สิ่งที่เหลืออยู่เป็นเพียงวิธีการทำสิ่งนั้นให้สำเร็จ
ใน Windows ส่วนที่สำคัญจะอยู่ในกระบวนการของคุณ mutex สามารถแชร์ / เข้าถึงระหว่างกระบวนการต่างๆ โดยทั่วไปส่วนที่สำคัญมีราคาถูกกว่ามาก ไม่สามารถแสดงความคิดเห็นเกี่ยวกับ Linux โดยเฉพาะ แต่ในบางระบบพวกเขาเป็นเพียงนามแฝงสำหรับสิ่งเดียวกัน
เพียงเพื่อเพิ่ม 2 เซ็นต์ของฉันส่วนที่สำคัญจะถูกกำหนดเป็นโครงสร้างและการดำเนินการกับพวกเขาจะดำเนินการในบริบทโหมดผู้ใช้
ntdll! _RTL_CRITICAL_SECTION + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG + 0x004 LockCount: Int4B + 0x008 RecursionCount: Int4B + 0x00c เป็นเจ้าของกระทู้: Ptr32 Void + 0x010 LockSemaphore: Ptr32 Void + 0x014 SpinCount: Uint4B
ในขณะที่ mutex เป็นเคอร์เนลอ็อบเจ็กต์ (ExMutantObjectType) ที่สร้างในไดเร็กทอรีอ็อบเจ็กต์ Windows การดำเนินการ Mutex ส่วนใหญ่จะใช้ในโหมดเคอร์เนล ตัวอย่างเช่นเมื่อสร้าง Mutex คุณจะเรียก nt! NtCreateMutant ในเคอร์เนล
คำตอบที่ดีจาก Michael ฉันได้เพิ่มการทดสอบที่สามสำหรับคลาส mutex ที่แนะนำใน C ++ 11 ผลลัพธ์ค่อนข้างน่าสนใจและยังคงสนับสนุนการรับรอง CRITICAL_SECTION ออบเจ็กต์สำหรับกระบวนการเดียว
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);
ผลลัพธ์ของฉันคือ 217, 473 และ 19 (โปรดทราบว่าอัตราส่วนของเวลาของฉันสำหรับสองครั้งล่าสุดนั้นเทียบได้กับของ Michael แต่เครื่องของฉันมีอายุน้อยกว่าของเขาอย่างน้อยสี่ปีดังนั้นคุณจะเห็นหลักฐานของความเร็วที่เพิ่มขึ้นระหว่างปี 2009 ถึง 2013 , เมื่อ XPS-8700 ออกมา) คลาส mutex ใหม่เร็วกว่า Windows mutex สองเท่า แต่ยังน้อยกว่าหนึ่งในสิบของความเร็วของวัตถุ Windows CRITICAL_SECTION โปรดทราบว่าฉันทดสอบเฉพาะ mutex แบบไม่เรียกซ้ำเท่านั้น CRITICAL_SECTION ออบเจ็กต์แบบวนซ้ำ (หนึ่งเธรดสามารถป้อนซ้ำได้หากออกจากจำนวนครั้งเท่ากัน)
ฟังก์ชัน AC เรียกว่า reentrant หากใช้พารามิเตอร์จริงเท่านั้น
ฟังก์ชัน Reentrant สามารถเรียกใช้โดยหลายเธรดในเวลาเดียวกันได้
ตัวอย่างฟังก์ชัน reentrant:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
ตัวอย่างฟังก์ชัน non reentrant:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
ไลบรารีมาตรฐาน C strtok () ไม่ได้ reentrant และไม่สามารถใช้โดย 2 เธรดหรือมากกว่าในเวลาเดียวกัน
SDK บางแพลตฟอร์มมาพร้อมกับ strtok () เวอร์ชัน reentrant ที่เรียกว่า strtok_r ();
Enrico Migliore