วัตถุประสงค์ของการเขียนโปรแกรมพร้อมกันคือการทำให้โปรแกรมทำงานได้เร็วขึ้น แต่การใช้พร้อมกันอาจไม่จำเป็นต้องทำให้โปรแกรมทำงานได้เร็วขึ้น ข้อดีของการเขียนโปรแกรมพร้อมกันสามารถสะท้อนได้ก็ต่อเมื่อจำนวนโปรแกรมพร้อมกันถึงลำดับความสำคัญ ดังนั้นจึงมีความหมายเท่านั้นที่จะพูดคุยเกี่ยวกับการเขียนโปรแกรมพร้อมกันเมื่อมีการพร้อมกันสูง แม้ว่าจะยังไม่มีการพัฒนาโปรแกรมที่มีปริมาณการทำงานพร้อมกันสูง แต่การเรียนรู้พร้อมกันคือการเข้าใจสถาปัตยกรรมแบบกระจายบางอย่างได้ดีขึ้น จากนั้นเมื่อปริมาณการเกิดขึ้นพร้อมกันของโปรแกรมไม่สูงเช่นโปรแกรมเธรดเดียวประสิทธิภาพการดำเนินการของเธรดเดี่ยวจะสูงกว่าโปรแกรมมัลติเธรด ทำไมถึงเป็นเช่นนี้? ผู้ที่คุ้นเคยกับระบบปฏิบัติการควรรู้ว่า CPU ใช้การมัลติเธรดมัลติเธรดโดยการจัดสรรเวลาเวลาให้กับแต่ละเธรด ด้วยวิธีนี้เมื่อ CPU เปลี่ยนจากงานหนึ่งไปอีกงานหนึ่งสถานะของงานก่อนหน้าจะถูกบันทึกไว้ เมื่องานถูกดำเนินการ CPU จะดำเนินการต่อเพื่อดำเนินการสถานะของงานก่อนหน้า กระบวนการนี้เรียกว่าการสลับบริบท
ใน Java multithreading คำหลักที่ซิงโครไนซ์ของคำหลักที่ผันผวนมีบทบาทสำคัญ พวกเขาทั้งหมดสามารถใช้การซิงโครไนซ์เธรดได้ แต่มันถูกนำไปใช้ที่ด้านล่างอย่างไร
ระเหย
ความผันผวนสามารถตรวจสอบให้แน่ใจว่าการมองเห็นตัวแปรของแต่ละเธรด แต่ไม่สามารถรับประกันความเป็นอะตอมได้ ฉันจะไม่พูดอะไรมากเกี่ยวกับวิธีการใช้ความผันผวนของภาษา Java ข้อเสนอแนะของฉันคือการใช้มันในสถานการณ์อื่น ๆ ยกเว้นไลบรารีคลาสในแพ็คเกจ java.util.concurrent.atomic ดูบทความนี้สำหรับคำอธิบายเพิ่มเติม
การแนะนำ
ดูรหัสต่อไปนี้
แพ็คเกจ org.go; คลาสสาธารณะไป {ผันผวน int i = 0; Private Void Inc () {i ++; } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {go go = new go (); สำหรับ (int i = 0; i <10; i ++) {เธรดใหม่ (() -> {สำหรับ (int j = 0; j <1000; j ++) go.inc ();}). start (); } ในขณะที่ (thread.activeCount ()> 1) {thread.yield (); } system.out.println (go.i); - ผลลัพธ์ของการดำเนินการแต่ละรหัสข้างต้นนั้นแตกต่างกันและหมายเลขเอาต์พุตจะน้อยกว่า 10,000 เสมอนี่เป็นเพราะเมื่อดำเนินการ INC () I ++ ไม่ใช่การทำงานของอะตอม บางทีบางคนอาจแนะนำให้ใช้การซิงโครไนซ์เพื่อซิงโครไนซ์ Inc () หรือใช้ล็อคภายใต้แพ็คเกจ java.util.concurrent.locks เพื่อควบคุมการซิงโครไนซ์เธรด แต่พวกเขาไม่ดีเท่าโซลูชั่นต่อไปนี้:
แพ็คเกจ org.go; นำเข้า java.util.concurrent.atomic.atomicinteger; คลาสสาธารณะไป {Atomicinteger i = ใหม่ atomicinteger (0); Private Void Inc () {i.getandincrement (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {go go = new go (); สำหรับ (int i = 0; i <10; i ++) {เธรดใหม่ (() -> {สำหรับ (int j = 0; j <1000; j ++) go.inc ();}). start (); } ในขณะที่ (thread.activeCount ()> 1) {thread.yield (); } system.out.println (go.i); - ในเวลานี้หากคุณไม่เข้าใจการใช้อะตอมคุณจะสงสัยอย่างแน่นอนว่า Atomicinteger อาจนำไปใช้โดยใช้ล็อคดังนั้นจึงอาจไม่มีประสิทธิภาพ แล้วคืออะไรกันมาดูกันเถอะ
การใช้งานภายในชั้นเรียนอะตอม
ไม่ว่าจะเป็น Atomicinteger หรือพร้อมกัน
Private Static Final sun.misc.unsafe unsafe;, คลาสนี้เป็น java encapsulation ของ Sun :: misc :: unsafe ที่ใช้ความหมายอะตอม ฉันต้องการเห็นการใช้งานพื้นฐาน ฉันมีรหัสที่มาของ GCC4.8 อยู่ในมือ เมื่อเทียบกับเส้นทางท้องถิ่นมันสะดวกมากที่จะหาเส้นทางไปยัง GitHub ดูที่นี่
นำตัวอย่างการใช้งานของอินเทอร์เฟซ getandincrement ()
Atomicinteger.java
ส่วนตัวคงที่สุดท้ายไม่ปลอดภัย unsafe = unsafe.getunsafe (); public final int getandincrement () {สำหรับ (;;) {int current = get (); int next = current + 1; ถ้า (เปรียบเทียบ (ปัจจุบัน, ถัดไป)) ส่งคืนกระแส; }} public Boolean Final PomperEndset (int คาดหวัง, การอัปเดต int) {return unsafe.compareandswapint (นี่, valueOffset, คาดหวัง, อัปเดต); - ให้ความสนใจกับสิ่งนี้สำหรับลูปมันจะกลับมาหากการเปรียบเทียบประสบความสำเร็จ มิฉะนั้นมันจะเปรียบเทียบกันเสมอ
การใช้งานการเปรียบเทียบเซ็ตเรียกว่า ที่นี่ฉันสังเกตเห็นว่าการใช้ Oracle JDK นั้นแตกต่างกันเล็กน้อย หากคุณดูที่ SRC ภายใต้ JDK คุณจะเห็นว่า Oracle JDK เรียกว่าไม่ปลอดภัย getandincrement () แต่ฉันเชื่อว่าเมื่อ Oracle JDK ใช้ unsafe.java มันควรเรียกใช้การเปรียบเทียบเท่านั้น
unsafe.java
Public Native Boolean Polandswapint (Object OBJ, ชดเชยยาว, คาดหวัง int, การอัปเดต int);
การใช้งาน C ++ เรียกผ่าน JNI
natunsafe.cc
jboolansun :: misc :: unsafe :: เปรียบเทียบและ wapevapint (Jobject obj, jlong offset, jint คาดหวัง, jint update) {jint *addr = (jint *) ((char *) obj + ออฟเซ็ต); ส่งคืน (addr, คาดหวัง, อัปเดต);} inline inline boolcompareandswap (ผันผวน jint *addr, jint old, jint new_val) {jboolean result = false; สปินล็อคล็อค; if ((result = ( *addr == เก่า)) *addr = new_val; ผลตอบแทนผลลัพธ์;} ไม่ปลอดภัย :: Compereandswapint เรียกฟังก์ชั่นสแตติกเปรียบเทียบกันและสเว็ป เปรียบเทียบและ Spinlock เป็นล็อค สปินล็อคที่นี่มีความหมายของ Lockguard ซึ่งถูกล็อคระหว่างการก่อสร้างและปล่อยออกมาในระหว่างการทำลายล้าง
เราต้องมุ่งเน้นไปที่สปินล็อค นี่คือเพื่อให้แน่ใจว่า Spinlock เป็นการดำเนินการที่แท้จริงของการดำเนินการอะตอมก่อนที่จะเปิดตัว
Spinlock คืออะไร
สปินล็อคยุ่งอยู่กับการรอการล็อคทรัพยากร ซึ่งแตกต่างจากการปิดกั้นเธรดปัจจุบันของ Mutex และปล่อยทรัพยากร CPU เพื่อรอทรัพยากรที่จำเป็น Spinlock จะไม่เข้าสู่กระบวนการระงับรอเงื่อนไขที่จะปฏิบัติตามและแข่งขัน CPU อีกครั้ง ซึ่งหมายความว่าสปินล็อคดีกว่า mutex เฉพาะในกรณีที่ค่าใช้จ่ายในการรอการล็อคน้อยกว่าค่าใช้จ่ายของสวิตช์บริบทการดำเนินการเธรด
natunsafe.cc
คลาส spinlock {ระเหยแบบคงที่ obj_addr_t ล็อค; สาธารณะ: spinlock () {ในขณะที่ (! compare_and_swap (& ล็อค, 0, 1)) _jv_threadyield (); } ~ spinlock () {release_set (& lock, 0); - ใช้ตัวแปรสแตติกระเหยแบบคงที่ obj_addr_t ล็อค; ในฐานะที่เป็นบิตธงตัวป้องกันจะถูกนำไปใช้ผ่าน C ++ RAII ดังนั้นการล็อคที่เรียกว่าเป็นตัวแปรสมาชิกคงที่ OBJ_ADDR_T LOCK ความผันผวนใน C ++ ไม่สามารถรับประกันการซิงโครไนซ์ สิ่งที่รับประกันได้คือ compare_and_swap ที่เรียกว่าในตัวสร้างและล็อคตัวแปรคงที่ เมื่อตัวแปรล็อคนี้คือ 1 คุณต้องรอ เมื่อเป็น 0 คุณตั้งค่าเป็น 1 ผ่านการทำงานของอะตอมซึ่งบ่งชี้ว่าคุณได้รับการล็อค
มันเป็นอุบัติเหตุจริง ๆ ที่จะใช้ตัวแปรคงที่ที่นี่ซึ่งหมายความว่าโครงสร้างที่ปราศจากล็อคทั้งหมดแบ่งปันตัวแปรเดียวกัน (จริงขนาดจริง) เพื่อแยกแยะว่าจะเพิ่มการล็อคหรือไม่ เมื่อตัวแปรนี้ถูกตั้งค่าเป็น 1 ต้องรอสปินล็อคอื่น ๆ ทำไมไม่เพิ่มตัวแปรส่วนตัวที่ระเหยได้ OBJ_ADDR_T ล็อคใน Sun :: Misc :: unsafe และส่งผ่านไปยัง Spinlock เป็นพารามิเตอร์ตัวสร้าง? สิ่งนี้เทียบเท่ากับการแบ่งปันบิตธงสำหรับแต่ละอันที่ไม่ปลอดภัย เอฟเฟกต์จะดีขึ้นหรือไม่?
_jv_threadyield ในไฟล์ต่อไปนี้ทรัพยากร CPU จะถูกส่งผ่านการโทรของระบบ sched_yield (man 2 sched_yield) แมโคร HAD_SCHED_YIELD ถูกกำหนดไว้ในการกำหนดค่าซึ่งหมายความว่าหากคำจำกัดความไม่ได้กำหนดระหว่างการรวบรวมสปินล็อคจะเรียกว่าล็อคสปินที่แท้จริง
posix-threads.h
inline void_jv_threadyield (void) {#ifdef have_sched_yield sched_yield ();#endif / * have_sched_yield * /} lock.h นี้มีการใช้งานที่แตกต่างกันบนแพลตฟอร์มที่แตกต่างกัน เราใช้แพลตฟอร์ม IA64 (Intel AMD x64) เป็นตัวอย่าง การใช้งานอื่น ๆ สามารถดูได้ที่นี่
ia64/locks.h
typedef size_t obj_addr_t; inline static boolcompare_and_swap (ผันผวน obj_addr_t *addr, obj_addr_t เก่า, obj_addr_t new_val) {return __sync_bool_compare_and_swap obj_addr_t *addr, obj_addr_t new_val) {__ASM__ __Volatile __ ("" ::::: ":" หน่วยความจำ "); *(addr) = new_val;}__SYNC_BOOL_COMPARE_AND_SWAP เป็นฟังก์ชั่น GCC ในตัวและคำสั่งการประกอบ "หน่วยความจำ" ทำให้สิ่งกีดขวางหน่วยความจำเสร็จสมบูรณ์
ในระยะสั้นฮาร์ดแวร์ทำให้มั่นใจได้ว่าการซิงโครไนซ์ CPU แบบหลายคอร์และการใช้งานที่ไม่ปลอดภัยนั้นมีประสิทธิภาพมากที่สุด GCC-Java ค่อนข้างมีประสิทธิภาพฉันเชื่อว่า Oracle และ OpenJDK จะไม่เลวร้ายลง
การดำเนินการอะตอมและการดำเนินการในตัว GCC ในตัว
การทำงานของอะตอม
นิพจน์ Java และการแสดงออกของ C ++ ไม่ใช่การดำเนินการอะตอมซึ่งหมายความว่าคุณอยู่ในรหัส:
// สมมติว่าฉันเป็นตัวแปร i ++ ที่แชร์ระหว่างเธรด;
ในสภาพแวดล้อมแบบมัลติเธรดฉันเข้าถึงไม่ใช่อะตอมและถูกแบ่งออกเป็นสามตัวถูกดำเนินการต่อไปนี้:
คอมไพเลอร์เปลี่ยนระยะเวลาของการดำเนินการดังนั้นจึงไม่คาดว่าจะมีผลการดำเนินการ
การทำงานของอะตอมในตัว GCC
GCC มีการดำเนินการอะตอมในตัวซึ่งเพิ่มขึ้นจาก 4.1.2 ก่อนหน้านี้พวกเขาถูกนำไปใช้โดยใช้ชุดประกอบแบบอินไลน์
พิมพ์ __sync_fetch_and_add (ประเภท *ptr, ประเภทประเภท, ... ) พิมพ์ __sync_fetch_and_sub (ประเภท *ptr, ค่าประเภท, ... ) พิมพ์ __sync_fetch_and_or (ประเภท *ptr, ประเภท, ... ค่า, ... ) พิมพ์ __sync_fetch_and_xor (ประเภท *ptr, ค่าประเภท, ... ) ประเภท __sync_fetch_and_nand (ประเภท *ptr, ค่าประเภท, ... ) ประเภท __sync_add_and_fetch (ประเภท *ptr, ค่าประเภท, ... (ประเภท *ptr, ค่าประเภท, ... ) พิมพ์ __sync_and_and_fetch (ประเภท *ptr, ค่าประเภท, ... ) ประเภท __sync_and_and_fetch (ประเภท *ptr, ค่าประเภท, ... ) ประเภท __sync_xor_and_fetch (ประเภท *ptr, ประเภท __sync_bool_compare_and_swap (ประเภท *ptr, ประเภท oldval type newval, ... ) ประเภท __sync_val_compare_and_swap (ประเภท *ptr, ประเภท oldval type newval, ... ) __ sync_synchronize __sync_lock_release (ประเภท *ptr, ... )
สิ่งที่ควรสังเกตคือ:
ไฟล์ที่เกี่ยวข้องกับ OpenJDK
ด้านล่างนี้คือการใช้งานอะตอมของ OpenJDK9 บน GitHub โดยหวังว่าจะช่วยเหลือผู้ที่จำเป็นต้องรู้ ท้ายที่สุด OpenJDK นั้นใช้กันอย่างแพร่หลายมากกว่า GCC - แต่ท้ายที่สุดก็ไม่มีซอร์สโค้ดสำหรับ Oracle JDK แม้ว่าจะมีการกล่าวกันว่าซอร์สโค้ดระหว่าง OpenJDK และ Oracle นั้นเล็กมาก
Atomicinteger.java
unsafe.java::compareandexchangeObject
unsafe.cpp :: unsafe_compareandexchangeObject
oop.inline.hpp :: oopdesc :: atomic_compare_exchange_oop
Atomic_linux_x86.hpp :: atomic :: cmpxchg
inline jlong atomic :: cmpxchg (jlong exchange_value, ระเหย jlong* dest, jlong compare_value, cmmpxchg_memory_order) {bool mp = os :: is_mp (); __ASM__ __volatile__ (LOCK_IF_MP (%4) "CMPXCHGQ%1, (%3)": "= A" (Exchange_Value): "R" (Exchange_value), "A" (Compare_value), "r" (dest), "r" (mp): ส่งคืน exchange_value;} ที่นี่เราต้องให้คำแนะนำแก่โปรแกรมเมอร์ Java ที่ไม่คุ้นเคยกับ C/C ++ รูปแบบของคำแนะนำการประกอบแบบฝังมีดังนี้
__ASM__ [__volatile __] (เทมเพลตแอสเซมบลี // เทมเพลตแอสเซมบลี: [รายการตัวถูกดำเนินการเอาต์พุต] // รายการอินพุต: [รายการอินพุตตัวถูกดำเนินการ] // รายการเอาต์พุต: [รายการ clobber]) // รายการทำลาย
%1, %3, %4 ในเทมเพลตแอสเซมบลีสอดคล้องกับรายการพารามิเตอร์ต่อไปนี้ {"r" (exchange_value), "r" (dest), "r" (mp)}, และรายการพารามิเตอร์ถูกคั่นด้วยเครื่องหมายจุลภาค "R" หมายถึงการลงทะเบียนทั่วไป "A" หมายถึงการลงทะเบียน EAX และ "=" หมายถึงเอาต์พุต (เขียนกลับ) คำสั่ง CMPXCHG หมายถึงการใช้ EAX registers นั่นคือพารามิเตอร์ %2
รายละเอียดอื่น ๆ จะไม่อยู่ในรายการที่นี่ การดำเนินการของ GCC คือการผ่านตัวชี้ที่จะแลกเปลี่ยนและหลังจากการเปรียบเทียบที่ประสบความสำเร็จค่าจะถูกกำหนดโดยตรง (การกำหนดที่ไม่ใช่อะตอม) และการรับประกันของอะตอมโดย Spinlock
การใช้งาน OpenJDK คือการส่งตัวชี้ที่จะแลกเปลี่ยนและกำหนดค่าโดยตรงผ่านคำสั่งประกอบ CMMPXCHGQ และการรับประกันความเป็นอะตอมผ่านคำสั่งประกอบ แน่นอนชั้นพื้นฐานของ Spinlock ของ GCC นั้นรับประกันได้ผ่าน CMMPXCHGQ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น