ฉันได้ยินมาว่า i ++ ไม่ปลอดภัยต่อเธรด ++ i เธรดปลอดภัยหรือไม่


90

ฉันได้ยินมาว่า i ++ ไม่ใช่คำสั่งที่ปลอดภัยต่อเธรดเนื่องจากในการประกอบมันจะลดลงไปที่การจัดเก็บค่าดั้งเดิมเป็นอุณหภูมิที่ใดที่หนึ่งเพิ่มขึ้นแล้วแทนที่ซึ่งอาจถูกขัดจังหวะโดยสวิตช์บริบท

อย่างไรก็ตามฉันสงสัยเกี่ยวกับ ++ i เท่าที่ฉันสามารถบอกได้สิ่งนี้จะลดลงเป็นคำสั่งประกอบเดียวเช่น 'เพิ่ม r1, r1, 1' และเนื่องจากเป็นเพียงคำสั่งเดียวจึงไม่สามารถขัดจังหวะได้โดยสวิตช์บริบท

ใครช่วยชี้แจงหน่อย ฉันสมมติว่ามีการใช้แพลตฟอร์ม x86


แค่คำถาม. สถานการณ์แบบใดที่ต้องการให้เธรดสองชุด (หรือมากกว่า) เข้าถึงตัวแปรเช่นนั้น ขอตรงๆตรงนี้ไม่วิจารณ์ แค่ชั่วโมงนี้หัวของฉันคิดอะไรไม่ออก
OscarRyz

5
ตัวแปรคลาสในคลาส C ++ ที่รักษาการนับวัตถุ?
paxdiablo

1
วิดีโอดีๆเกี่ยวกับเรื่องที่ฉันเพิ่งดูวันนี้เพราะผู้ชายอีกคนบอกฉัน: youtube.com/watch?v=mrvAqvtWYb4
Johannes Schaub - litb

1
ติดแท็กใหม่เป็น C / C ++; Java ไม่ได้รับการพิจารณาที่นี่ C # คล้ายกัน แต่ขาดความหมายของหน่วยความจำที่กำหนดไว้อย่างเข้มงวด
Tim Williscroft

1
@Oscar Reyes สมมติว่าคุณมีสองเธรดทั้งสองโดยใช้ตัวแปร i หากเธรดหนึ่งเพิ่มเธรดเมื่อถึงจุดหนึ่งและอีกเธรดจะลดลงเมื่อเธรดอยู่ที่จุดอื่นคุณจะต้องกังวลเกี่ยวกับความปลอดภัยของเธรด
samoz

คำตอบ:


158

คุณเคยได้ยินผิด อาจเป็นไปได้ว่า"i++"เธรดปลอดภัยสำหรับคอมไพเลอร์เฉพาะและสถาปัตยกรรมตัวประมวลผลเฉพาะ แต่ไม่ได้รับคำสั่งในมาตรฐานเลย ในความเป็นจริงเนื่องจากมัลติเธรดไม่ได้เป็นส่วนหนึ่งของมาตรฐาน ISO C หรือ C ++ (a)คุณจึงไม่สามารถพิจารณาอะไรที่จะปลอดภัยต่อเธรดได้ตามสิ่งที่คุณคิดว่าจะรวบรวมลงไป

ค่อนข้างเป็นไปได้ที่++iสามารถรวบรวมตามลำดับโดยพลการเช่น:

load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory

ซึ่งจะไม่ปลอดภัยต่อเธรดบน CPU (จินตภาพ) ของฉันที่ไม่มีคำแนะนำในการเพิ่มหน่วยความจำ หรืออาจจะฉลาดและรวบรวมเป็น:

lock         ; disable task switching (interrupts)
load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory
unlock       ; enable task switching (interrupts)

ที่lockปิดใช้งานและunlockเปิดใช้งานการขัดจังหวะ แต่ถึงอย่างนั้นสิ่งนี้อาจไม่ปลอดภัยต่อเธรดในสถาปัตยกรรมที่มีหน่วยความจำแชร์ซีพียูเหล่านี้มากกว่าหนึ่งตัว ( lockอาจปิดใช้งานการขัดจังหวะสำหรับ CPU ตัวเดียวเท่านั้น)

ภาษาเอง (หรือไลบรารีสำหรับมันหากไม่ได้ติดตั้งไว้ในภาษา) จะให้โครงสร้างที่ปลอดภัยสำหรับเธรดและคุณควรใช้สิ่งเหล่านี้แทนที่จะขึ้นอยู่กับความเข้าใจของคุณ (หรืออาจเข้าใจผิด) ว่าจะสร้างรหัสเครื่องใด

สิ่งเช่น Java synchronizedและpthread_mutex_lock()(สามารถใช้ได้กับ C / C ++ ภายใต้ระบบปฏิบัติการบางส่วน) เป็นสิ่งที่คุณต้องมองเข้าไปใน(ก)


(a)คำถามนี้ถูกถามก่อนที่มาตรฐาน C11 และ C ++ 11 จะเสร็จสมบูรณ์ ตอนนี้การทำซ้ำเหล่านั้นได้แนะนำการสนับสนุนเธรดในข้อกำหนดของภาษาซึ่งรวมถึงประเภทข้อมูลอะตอม (แม้ว่าโดยทั่วไปแล้วจะเป็นประเภทข้อมูลหรือเธรดหรือไม่ก็ได้ แต่อย่างน้อยก็ใน C)


8
+1 เพื่อย้ำว่านี่ไม่ใช่ปัญหาเฉพาะแพลตฟอร์มไม่ต้องพูดถึงคำตอบที่ชัดเจน ...
RBerteig

2
ขอแสดงความยินดีสำหรับป้ายเงิน C ของคุณ :)
Johannes Schaub - litb

ฉันคิดว่าคุณควรจะแม่นยำว่าไม่มีระบบปฏิบัติการสมัยใหม่ที่อนุญาตให้โปรแกรมโหมดผู้ใช้ปิดการขัดจังหวะและ pthread_mutex_lock () ไม่ได้เป็นส่วนหนึ่งของ C.
Bastien Léonard

@ บาสเตียนไม่มีระบบปฏิบัติการสมัยใหม่ที่จะทำงานบน CPU ที่ไม่มีคำสั่งเพิ่มหน่วยความจำ :-) แต่ประเด็นของคุณเกี่ยวกับ C.
paxdiablo

5
@ บาสเตียน: กระทิง. โดยทั่วไปโปรเซสเซอร์ RISC ไม่มีคำแนะนำในการเพิ่มหน่วยความจำ โหลด / เพิ่ม / stor tripplet คือวิธีที่คุณทำเช่นนั้นใน PowerPC
derobert

42

คุณไม่สามารถสร้างคำสั่งครอบคลุมเกี่ยวกับ ++ i หรือ i ++ ได้ ทำไม? พิจารณาเพิ่มจำนวนเต็ม 64 บิตบนระบบ 32 บิต เว้นแต่เครื่องต้นแบบจะมีคำสั่งรูปสี่เหลี่ยม "load, Increment, store" คำสั่งการเพิ่มค่านั้นจะต้องใช้คำสั่งหลายคำสั่งซึ่งอาจถูกขัดจังหวะโดยสวิตช์บริบทเธรด

นอกจาก++iนี้ไม่ควร "เพิ่มหนึ่งในค่า" ในภาษาเช่น C การเพิ่มตัวชี้จะเพิ่มขนาดของสิ่งที่ชี้ไป นั่นคือถ้าiเป็นตัวชี้ไปยังโครงสร้าง++i32 ไบต์ให้เพิ่ม 32 ไบต์ ในขณะที่เกือบทุกแพลตฟอร์มมีคำสั่ง "ค่าที่เพิ่มขึ้นที่ที่อยู่หน่วยความจำ" ซึ่งเป็นอะตอมไม่ใช่ทั้งหมดที่มีคำสั่ง "เพิ่มค่าตามอำเภอใจให้กับค่าที่อยู่หน่วยความจำ"


35
แน่นอนว่าถ้าคุณไม่ จำกัด ตัวเองให้อยู่กับจำนวนเต็ม 32 บิตที่น่าเบื่อในภาษาเช่น C ++ ++ ฉันสามารถเรียกใช้บริการเว็บที่อัปเดตค่าในฐานข้อมูลได้
คราส

16

ทั้งสองเธรดไม่ปลอดภัย

CPU ไม่สามารถคำนวณได้โดยตรงกับหน่วยความจำ ทำเช่นนั้นทางอ้อมโดยการโหลดค่าจากหน่วยความจำและทำการคำนวณด้วยการลงทะเบียน CPU

ผม ++

register int a1, a2;

a1 = *(&i) ; // One cpu instruction: LOAD from memory location identified by i;
a2 = a1;
a1 += 1; 
*(&i) = a1; 
return a2; // 4 cpu instructions

++ ผม

register int a1;

a1 = *(&i) ; 
a1 += 1; 
*(&i) = a1; 
return a1; // 3 cpu instructions

สำหรับทั้งสองกรณีมีเงื่อนไขการแย่งชิงที่ส่งผลให้ค่า i ไม่สามารถคาดเดาได้

ตัวอย่างเช่นสมมติว่ามีเธรด ++ i พร้อมกันสองเธรดโดยแต่ละเธรดใช้ register a1, b1 ตามลำดับ และด้วยการสลับบริบทจะดำเนินการดังต่อไปนี้:

register int a1, b1;

a1 = *(&i);
a1 += 1;
b1 = *(&i);
b1 += 1;
*(&i) = a1;
*(&i) = b1;

ผลก็คือฉันไม่กลายเป็น i + 2 กลายเป็น i + 1 ซึ่งไม่ถูกต้อง

ในการแก้ไขปัญหานี้ซีพียู moden จะให้คำแนะนำ LOCK, UNLOCK cpu บางประเภทในช่วงที่การสลับบริบทถูกปิด

บน Win32 ใช้ InterlockedIncrement () เพื่อทำ i ++ เพื่อความปลอดภัยของเธรด เร็วกว่าการใช้ mutex มาก


6
"CPU ไม่สามารถคำนวณด้วยหน่วยความจำโดยตรง" - ไม่ถูกต้อง มี CPU-s ที่คุณสามารถคำนวณ "โดยตรง" กับองค์ประกอบหน่วยความจำโดยไม่จำเป็นต้องโหลดลงในรีจิสเตอร์ก่อน เช่น. MC68000
darklon

1
คำสั่ง LOCK และ UNLOCK CPU ไม่มีส่วนเกี่ยวข้องกับสวิตช์บริบท พวกเขาล็อคสายแคช
David Schwartz

11

หากคุณกำลังแชร์แม้กระทั่ง int ข้ามเธรดในสภาพแวดล้อมแบบมัลติคอร์คุณต้องมีอุปสรรคด้านหน่วยความจำที่เหมาะสม ซึ่งอาจหมายถึงการใช้คำแนะนำที่เชื่อมต่อกัน (ดูตัวอย่างเช่น InterlockedIncrement ใน win32) หรือใช้ภาษา (หรือคอมไพเลอร์) ที่รับประกันเธรดที่ปลอดภัย ด้วยการเรียงลำดับคำสั่งระดับ CPU และแคชและปัญหาอื่น ๆ เว้นแต่คุณจะมีการรับประกันเหล่านั้นอย่าถือว่าสิ่งที่แชร์ข้ามเธรดนั้นปลอดภัย

แก้ไข: สิ่งหนึ่งที่คุณสามารถสันนิษฐานได้จากสถาปัตยกรรมส่วนใหญ่ก็คือหากคุณกำลังจัดการกับคำเดี่ยวที่จัดเรียงอย่างถูกต้องคุณจะไม่ลงเอยด้วยคำเดียวที่มีการรวมกันของค่าสองค่าที่ถูกบดเข้าด้วยกัน หากการเขียนสองรายการเกิดขึ้นทับกันคนหนึ่งจะชนะและอีกรายการจะถูกทิ้ง หากคุณระมัดระวังคุณสามารถใช้ประโยชน์จากสิ่งนี้และเห็นว่า ++ i หรือ i ++ ปลอดภัยต่อเธรดในสถานการณ์สำหรับผู้เขียนคนเดียว / ผู้อ่านหลายคน


ผิดจริงในสภาพแวดล้อมที่การเข้าถึง int (อ่าน / เขียน) เป็นปรมาณู มีอัลกอริทึมที่สามารถทำงานในสภาพแวดล้อมดังกล่าวได้แม้ว่าการไม่มีอุปสรรคด้านหน่วยความจำอาจหมายความว่าบางครั้งคุณกำลังทำงานกับข้อมูลเก่า
MSalters

2
ฉันแค่บอกว่าปรมาณูไม่ได้รับประกันความปลอดภัยของเธรด หากคุณฉลาดพอที่จะออกแบบโครงสร้างข้อมูลหรืออัลกอริทึมที่ไม่มีการล็อกให้ดำเนินการต่อ แต่คุณยังต้องรู้ว่าการรับประกันคอมไพเลอร์ของคุณจะให้อะไรแก่คุณ
Eclipse

10

หากคุณต้องการการเพิ่มขึ้นของอะตอมใน C ++ คุณสามารถใช้ไลบรารี C ++ 0x ( std::atomicประเภทข้อมูล) หรือบางอย่างเช่น TBB

มีอยู่ครั้งหนึ่งที่หลักเกณฑ์การเข้ารหัส GNU กล่าวว่าการอัปเดตประเภทข้อมูลที่พอดีในคำเดียวนั้น "มักจะปลอดภัย" แต่คำแนะนำนั้นไม่ถูกต้องสำหรับเครื่อง SMP ผิดสำหรับสถาปัตยกรรมบางอย่างและไม่ถูกต้องเมื่อใช้คอมไพเลอร์เพิ่มประสิทธิภาพ


หากต้องการชี้แจงความคิดเห็น "การอัปเดตประเภทข้อมูลคำเดียว":

เป็นไปได้ที่ซีพียูสองตัวบนเครื่อง SMP จะเขียนไปยังตำแหน่งหน่วยความจำเดียวกันในรอบเดียวกันจากนั้นจึงพยายามเผยแพร่การเปลี่ยนแปลงไปยังซีพียูอื่นและแคช แม้ว่าจะมีการเขียนข้อมูลเพียงคำเดียวดังนั้นการเขียนจึงใช้เวลาเพียงหนึ่งรอบเพื่อให้เสร็จสมบูรณ์ แต่ก็เกิดขึ้นพร้อมกันดังนั้นคุณไม่สามารถรับประกันได้ว่าการเขียนใดจะประสบความสำเร็จ คุณจะไม่ได้รับข้อมูลที่อัปเดตบางส่วน แต่การเขียนหนึ่งรายการจะหายไปเนื่องจากไม่มีวิธีอื่นในการจัดการกรณีนี้

เปรียบเทียบและสลับอย่างเหมาะสมระหว่างซีพียูหลายตัว แต่ไม่มีเหตุผลที่จะเชื่อว่าทุกการกำหนดตัวแปรของประเภทข้อมูลคำเดียวจะใช้การเปรียบเทียบและแลกเปลี่ยน

และแม้ว่าคอมไพลเลอร์ที่ปรับให้เหมาะสมจะไม่ส่งผลต่อวิธีการคอมไพล์ load / store แต่ก็สามารถเปลี่ยนแปลงได้เมื่อเกิด load / store ซึ่งก่อให้เกิดปัญหาร้ายแรงหากคุณคาดว่าการอ่านและเขียนของคุณจะเกิดขึ้นในลำดับเดียวกับที่ปรากฏในซอร์สโค้ด ( การล็อกที่มีชื่อเสียงที่สุดคือการตรวจสอบซ้ำไม่ทำงานในวานิลลา C ++)

หมายเหตุ คำตอบเดิมของฉันยังบอกด้วยว่าสถาปัตยกรรม Intel 64 บิตเสียในการจัดการกับข้อมูล 64 บิต นั่นไม่เป็นความจริงฉันจึงแก้ไขคำตอบ แต่การแก้ไขที่อ้างว่าชิป PowerPC เสีย นั่นเป็นความจริงเมื่ออ่านค่าทันที (เช่นค่าคงที่) ในรีจิสเตอร์ (ดูสองส่วนชื่อ "กำลังโหลดพอยน์เตอร์" ใต้รายการ 2 และรายการ 4) แต่มีคำสั่งสำหรับการโหลดข้อมูลจากหน่วยความจำในหนึ่งรอบ ( lmw) ดังนั้นฉันจึงลบคำตอบส่วนนั้นออกไป


การอ่านและการเขียนถือเป็นปรมาณูของ CPU ที่ทันสมัยที่สุดหากข้อมูลของคุณอยู่ในแนวเดียวกันและมีขนาดที่ถูกต้องแม้จะมี SMP และการปรับคอมไพเลอร์ให้เหมาะสมก็ตาม แม้ว่าจะมีข้อแม้มากมายโดยเฉพาะกับเครื่อง 64 บิตดังนั้นจึงอาจเป็นเรื่องยุ่งยากเพื่อให้แน่ใจว่าข้อมูลของคุณตรงตามความต้องการในทุกเครื่อง
Dan Olson

ขอบคุณสำหรับการอัปเดต ถูกต้องอ่านและเขียนเป็นปรมาณูในขณะที่คุณระบุว่าไม่สามารถทำได้ครึ่งเดียว แต่ความคิดเห็นของคุณเน้นว่าเราเข้าใกล้ข้อเท็จจริงนี้อย่างไรในทางปฏิบัติ เช่นเดียวกับอุปสรรคด้านความจำพวกเขาไม่ส่งผลกระทบต่อลักษณะอะตอมของการดำเนินการ แต่เราจะเข้าใกล้มันอย่างไรในทางปฏิบัติ
Dan Olson


4

หากการเขียนโปรแกรมภาษาของคุณบอกว่าไม่มีอะไรเกี่ยวกับหัวข้อที่ยังทำงานบนแพลตฟอร์มแบบมัลติเธรดวิธีที่สามารถใด ๆภาษาจะปลอดภัยต่อเธรดได้อย่างไร

ตามที่คนอื่น ๆ ชี้ให้เห็น: คุณต้องป้องกันการเข้าถึงตัวแปรแบบมัลติเธรดโดยการเรียกเฉพาะแพลตฟอร์ม

มีไลบรารีอยู่ที่นั่นซึ่งแยกความเฉพาะเจาะจงของแพลตฟอร์มออกไปและมาตรฐาน C ++ ที่กำลังจะมาถึงได้ปรับรูปแบบหน่วยความจำเพื่อรับมือกับเธรด (และสามารถรับประกันความปลอดภัยของเธรดได้)


4

แม้ว่าจะลดลงเป็นคำสั่งการประกอบเดี่ยว แต่การเพิ่มค่าในหน่วยความจำโดยตรงก็ยังไม่ปลอดภัยต่อเธรด

เมื่อเพิ่มค่าในหน่วยความจำฮาร์ดแวร์จะดำเนินการ "อ่าน - แก้ไข - เขียน" โดยจะอ่านค่าจากหน่วยความจำเพิ่มค่าและเขียนกลับไปยังหน่วยความจำ ฮาร์ดแวร์ x86 ไม่มีทางเพิ่มขึ้นโดยตรงบนหน่วยความจำ RAM (และแคช) สามารถอ่านและเก็บค่าได้เท่านั้นไม่สามารถแก้ไขได้

ตอนนี้สมมติว่าคุณมีสองคอร์แยกกันไม่ว่าจะเป็นบนซ็อกเก็ตแยกกันหรือแชร์ซ็อกเก็ตเดียว (มีหรือไม่มีแคชที่ใช้ร่วมกัน) โปรเซสเซอร์ตัวแรกอ่านค่าและก่อนที่จะสามารถเขียนกลับค่าที่อัพเดตได้โปรเซสเซอร์ตัวที่สองจะอ่าน หลังจากที่โปรเซสเซอร์ทั้งสองเขียนค่ากลับแล้วจะมีการเพิ่มขึ้นเพียงครั้งเดียวไม่ใช่สองครั้ง

มีวิธีหลีกเลี่ยงปัญหานี้ โปรเซสเซอร์ x86 (และโปรเซสเซอร์แบบมัลติคอร์ส่วนใหญ่ที่คุณจะพบ) สามารถตรวจจับความขัดแย้งประเภทนี้ในฮาร์ดแวร์และจัดลำดับได้เพื่อให้ลำดับการอ่านแก้ไข - เขียนทั้งหมดปรากฏเป็นอะตอม อย่างไรก็ตามเนื่องจากมีค่าใช้จ่ายสูงมากจึงทำได้เฉพาะเมื่อมีการร้องขอโดยรหัสโดยปกติแล้วบน x86 จะใช้LOCKคำนำหน้า สถาปัตยกรรมอื่น ๆ สามารถทำได้ในรูปแบบอื่นโดยมีผลลัพธ์ที่คล้ายคลึงกัน ตัวอย่างเช่น load-linked / store-conditional และ atomic Compare-and-swap (โปรเซสเซอร์ x86 ล่าสุดมีตัวสุดท้ายนี้ด้วย)

โปรดทราบว่าการใช้volatileไม่ได้ช่วยที่นี่ มันบอกคอมไพเลอร์เท่านั้นว่าตัวแปรอาจถูกแก้ไขจากภายนอกและอ่านว่าตัวแปรนั้นจะต้องไม่ถูกแคชในรีจิสเตอร์หรือปรับให้เหมาะสม มันไม่ได้ทำให้คอมไพเลอร์ใช้ปรมาณูแบบปรมาณู

วิธีที่ดีที่สุดคือใช้ atomic primitives (หากคอมไพเลอร์หรือไลบรารีของคุณมี) หรือทำการเพิ่มในแอสเซมบลีโดยตรง


2

อย่าคิดว่าส่วนที่เพิ่มขึ้นจะรวมเข้ากับการทำงานของอะตอม ใช้ InterlockedIncrement หรือฟังก์ชันที่คล้ายคลึงกันที่มีอยู่บนแพลตฟอร์มเป้าหมายของคุณ

แก้ไข: ฉันเพิ่งค้นหาคำถามเฉพาะนี้และการเพิ่มขึ้นของ X86 นั้นเป็นแบบปรมาณูบนระบบโปรเซสเซอร์เดี่ยว แต่ไม่ใช่ในระบบมัลติโปรเซสเซอร์ การใช้คำนำหน้าล็อคสามารถทำให้เป็นปรมาณู แต่มันพกพาสะดวกกว่ามากเพียงแค่ใช้ InterlockedIncrement


1
InterlockedIncrement () เป็นฟังก์ชันของ Windows กล่อง Linux และเครื่อง OS X ที่ทันสมัยทั้งหมดของฉันใช้ x64 ดังนั้นการพูดว่า InterlockedIncrement () นั้น 'พกพาได้มากกว่า' โค้ด x86 นั้นค่อนข้างปลอม
Pete Kirkham

พกพาสะดวกกว่ามากในแง่เดียวกับที่ C พกพาได้มากกว่าแอสเซมบลี เป้าหมายคือเพื่อป้องกันตัวเองจากการใช้ชุดประกอบที่สร้างขึ้นเฉพาะสำหรับโปรเซสเซอร์เฉพาะ หากระบบปฏิบัติการอื่นเป็นปัญหาของคุณ InterlockedIncrement จะถูกห่อหุ้มอย่างง่ายดาย
Dan Olson

2

ตามบทเรียนการประกอบบน x86 นี้คุณสามารถเพิ่มรีจิสเตอร์ไปยังตำแหน่งหน่วยความจำแบบอะตอมได้ดังนั้นโค้ดของคุณอาจเรียกใช้งาน '++ i' ou 'i ++' โดยอะตอม แต่ตามที่กล่าวไว้ในโพสต์อื่น C ansi ไม่ได้ใช้ atomicity กับการดำเนินการ '++' ดังนั้นคุณจึงไม่สามารถมั่นใจได้ว่าคอมไพเลอร์ของคุณจะสร้างอะไร


1

มาตรฐาน C ++ ปี 1998 ไม่มีอะไรจะพูดเกี่ยวกับเธรดแม้ว่ามาตรฐานถัดไป (ครบกำหนดปีนี้หรือปีหน้า) จะทำ ดังนั้นคุณไม่สามารถพูดอะไรที่ชาญฉลาดเกี่ยวกับความปลอดภัยของเธรดของการดำเนินงานโดยไม่อ้างถึงการนำไปใช้ ไม่ใช่แค่โปรเซสเซอร์ที่ใช้ แต่เป็นการรวมกันของคอมไพเลอร์ระบบปฏิบัติการและโมเดลเธรด

ในกรณีที่ไม่มีเอกสารในทางตรงกันข้ามฉันจะไม่ถือว่าการกระทำใด ๆ นั้นปลอดภัยต่อเธรดโดยเฉพาะอย่างยิ่งกับโปรเซสเซอร์แบบมัลติคอร์ (หรือระบบมัลติโปรเซสเซอร์) ฉันจะไม่เชื่อการทดสอบเนื่องจากปัญหาการซิงโครไนซ์เธรดมักจะเกิดขึ้นโดยบังเอิญเท่านั้น

ไม่มีสิ่งใดที่ปลอดภัยต่อเธรดเว้นแต่คุณจะมีเอกสารที่ระบุว่าเป็นระบบเฉพาะที่คุณใช้อยู่


1

โยนฉันลงในที่จัดเก็บเธรดในเครื่อง มันไม่ใช่ปรมาณู แต่มันก็ไม่สำคัญ


1

AFAIK ตามมาตรฐาน C ++ อ่าน / เขียนเป็นintอะตอม

อย่างไรก็ตามสิ่งที่ทำได้คือการกำจัดพฤติกรรมที่ไม่ได้กำหนดซึ่งเกี่ยวข้องกับการแย่งชิงข้อมูล

iแต่จะยังคงมีการแข่งขันข้อมูลหากกระทู้ทั้งสองพยายามที่จะเพิ่มขึ้น

ลองนึกภาพสถานการณ์ต่อไปนี้:

Let i = 0แรก:

เธรด A อ่านค่าจากหน่วยความจำและเก็บไว้ในแคชของตัวเอง เธรด A เพิ่มค่าทีละ 1

เธรด B อ่านค่าจากหน่วยความจำและเก็บไว้ในแคชของตัวเอง เธรด B เพิ่มค่าทีละ 1

หากนี่เป็นเธรดเดียวคุณจะได้รับi = 2ในความทรงจำ

แต่ด้วยเธรดทั้งสองเธรดแต่ละเธรดจะเขียนการเปลี่ยนแปลงดังนั้นเธรด A จึงเขียนi = 1กลับไปที่หน่วยความจำและเธรด B i = 1จะเขียนลงในหน่วยความจำ

มีการกำหนดไว้อย่างดีไม่มีการทำลายหรือการก่อสร้างบางส่วนหรือการฉีกขาดของวัตถุ แต่ก็ยังเป็นการแย่งชิงข้อมูล

ในการเพิ่มอะตอมiคุณสามารถใช้:

std::atomic<int>::fetch_add(1, std::memory_order_relaxed)

สามารถใช้การสั่งซื้อแบบผ่อนคลายได้เนื่องจากเราไม่สนใจว่าการดำเนินการนี้จะเกิดขึ้นที่ใดสิ่งที่เราสนใจก็คือการดำเนินการเพิ่มขึ้นเป็นปรมาณู


0

คุณบอกว่า "มันเป็นเพียงคำสั่งเดียวมันจะถูกปิดกั้นโดยสวิตช์บริบท" - ทั้งหมดนั้นดีและดีสำหรับ CPU ตัวเดียว แต่ CPU ดูอัลคอร์ล่ะ? จากนั้นคุณสามารถมีสองเธรดที่เข้าถึงตัวแปรเดียวกันพร้อมกันโดยไม่ต้องสลับบริบทใด ๆ

โดยไม่ต้องรู้ภาษาคำตอบคือการทดสอบ heck out.


4
คุณไม่ทราบว่ามีบางสิ่งที่ปลอดภัยต่อเธรดหรือไม่โดยการทดสอบ - ปัญหาเธรดอาจเกิดขึ้นได้หนึ่งในล้านครั้ง คุณค้นหาในเอกสารของคุณ หากเอกสารของคุณไม่รับประกันว่าเธรดจะปลอดภัย
Eclipse

2
เห็นด้วยกับ @Josh ที่นี่ มีบางอย่างเท่านั้นที่ปลอดภัยต่อเธรดหากสามารถพิสูจน์ได้ทางคณิตศาสตร์ผ่านการวิเคราะห์รหัสที่อยู่เบื้องหลัง ไม่มีการทดสอบจำนวนมากที่สามารถเริ่มเข้าใกล้ได้
Rex M

มันเป็นคำตอบที่ดีจนถึงประโยคสุดท้าย
Rob K

0

ฉันคิดว่าถ้านิพจน์ "i ++" เป็นเพียงนิพจน์เดียวก็เท่ากับ "++ i" คอมไพเลอร์จะฉลาดพอที่จะไม่เก็บค่าชั่วคราวเป็นต้นดังนั้นหากคุณสามารถใช้แทนกันได้ (มิฉะนั้นคุณจะชนะ อย่าถามว่าจะใช้อันไหน) ไม่ว่าคุณจะใช้อันไหนเพราะมันเกือบจะเหมือนกันหมด (ยกเว้นเรื่องความสวยงาม)

อย่างไรก็ตามแม้ว่าตัวดำเนินการส่วนเพิ่มจะเป็นแบบปรมาณู แต่ก็ไม่ได้รับประกันว่าการคำนวณที่เหลือจะสอดคล้องกันหากคุณไม่ได้ใช้การล็อกที่ถูกต้อง

หากคุณต้องการทดลองด้วยตัวเองให้เขียนโปรแกรมโดยที่ N เธรดจะเพิ่มขึ้นพร้อมกันตัวแปรที่ใช้ร่วมกัน M คูณแต่ละตัว ... ถ้าค่าน้อยกว่า N * M แสดงว่าส่วนเพิ่มบางส่วนถูกเขียนทับ ลองทั้ง preincrement และ postincrement แล้วบอกเรา ;-)


0

สำหรับตัวนับฉันขอแนะนำให้ใช้การเปรียบเทียบและแลกเปลี่ยนสำนวนซึ่งทั้งไม่ล็อคและปลอดภัยต่อเธรด

นี่คือใน Java:

public class IntCompareAndSwap {
    private int value = 0;

    public synchronized int get(){return value;}

    public synchronized int compareAndSwap(int p_expectedValue, int p_newValue){
        int oldValue = value;

        if (oldValue == p_expectedValue)
            value = p_newValue;

        return oldValue;
    }
}

public class IntCASCounter {

    public IntCASCounter(){
        m_value = new IntCompareAndSwap();
    }

    private IntCompareAndSwap m_value;

    public int getValue(){return m_value.get();}

    public void increment(){
        int temp;
        do {
            temp = m_value.get();
        } while (temp != m_value.compareAndSwap(temp, temp + 1));

    }

    public void decrement(){
        int temp;
        do {
            temp = m_value.get();
        } while (temp > 0 && temp != m_value.compareAndSwap(temp, temp - 1));

    }
}

ดูเหมือนว่าจะคล้ายกับฟังก์ชัน test_and_set
samoz

1
คุณเขียนว่า "ไม่ล็อก" แต่ไม่ "ซิงโครไนซ์" หมายถึงการล็อกใช่หรือไม่
Corey Trager
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.