Objective-C Programming Chapter 14 (Part 2)

Mutual Exclusion

จากปัญหา race condition เราสามารถใช้ Mutual Exclusive Access ซึ่งเป็นการกำหนดส่วนที่ให้เทรดสามารถเข้าใช้ทรัพยากรได้ทีละเทรด เราเรียกพื้นที่ตรงส่วนนี้ว่า Critical Section และการขอเข้าใช้ทรัพยากรส่วนนี้จะเรียกว่า mutex lock การทำงานของ mutual exclusive access เหมือนเราขอลูกกุญแจเพื่อเข้าไปเปิดประตู เมื่อเราเข้าไปในห้องแล้วประตูก็จะปิดอัตโนมัติไม่ให้คนอื่นเข้าไปใช้ได้นอกจากคนที่มีกุญแจ หลังจากทำงานเสร็จเราก็ต้องคืนลูกกุญแจเพื่อให้คนอื่นได้ใช้ต่อไป การใช้ mutex lock นี้สามารถทำได้โดยการใช้การใช้คลาส NSLock เราจะแก้ไขคลาส Producer โดยเพิ่มการเพิ่ม NSLock ดังนี้

Program 14.6
Producer.h

Producer.m

คลาส Producer มีสมาชิกใหม่คือ _mutexLock ซึ่งเป็นคลาส NSLock หน้าที่ของอ็อบเจ็กนี้คือเป็นตัวคอยควบคุมให้เทรดเข้าทำงานในส่วน critical section ได้ทีละเทรด และในเมธอด produceToStore ได้เพ่ิมโค้ด

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

ดังนั้นปัญหาที่ได้เกิดขึ้นไปเช่นโปรแกรมที่ 14.5 ก็จะแก้ไขแล้ว และผลลัพธ์ก็แสดงลักษณะเช่นนี้

Program 14.6 Output

Receive: Nikon camera 1
Receive: Cannon camera 2
Receive: Sony camera 3
Receive: Nikon camera 4
Receive: Cannon camera 5
Receive: Sony camera 6
Receive: Nikon camera 7
Receive: Cannon camera 8
Receive: Sony camera 9

mutex

จากรูปภาพแสดงให้เห็นการทำงานของ mutexLock คือเมื่อ camID ว่างก็จะอนุญาติให้เข้าใช้งาน แต่ถ้าไม่ว่างก็จะถูกปฎิเสธ

@synchronized()

นอกจากเราจะสามารถใช้ NSLock ยังมีอีกวิธีคือการใช้ @synchronized ซึ่งมีหลักการเหมือนกันกับ NSLock คือใช้เฉพาะส่วนที่เป็น critical section การใช้ @synchronized มีความสะดวกกว่า NSLock เนื่องจากสามารถใช้งานได้เลย ไม่ต้องประกาศอ็อบเจ็กให้ยุ่งยาก แต่เราจำเป็นต้องกำหนดอ็อบเจ็ก key ที่เป็นเอกลักษณ์ (unique) เพื่อที่ระบบจะได้แยกแยะระหว่าง @synchronized อื่นๆได้ เราจะเปลี่ยนโค้ดของคลาส Producer ให้เป็นดังนี้

Program 14.7
Producer.m

จากโค้ดได้กำหนดให้ key ของ @synchronized ให้เป็น self อย่างไรก็ตามเราจะกำหนด key เป็นอะไรก็ได้ดังเช่นตัวอย่าง

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

Avoiding thread problems

โปรแกรมที่มีการทำงานแบบ multithread มีประโยชน์อย่างมาก แต่ก็มีปัญหาที่ต้องคอยระวังอย่างเช่นปัญหา race condition นอกจากปัญหานี้แล้วยังมีปัญหาอื่นที่ควรระวัง เช่น

Deadlock ปัญหานี้เกิดจากเทรด A จะทำงานสำเร็จก็ต่อเมื่อได้ใช้ตัวแปรหรือทรัพยากรซึ่งต้องรอจากเทรด B แต่ในขณะเดียวกันเทรด B จะทำงานเสร็จก็ต่อเมื่อได้ทรัพยากรจากเทรด A ดังนั้นเทรดทั้งสองต่างก็จะรอทรัพยากรไปเรื่อยๆ ซึ่งเขียนเป็นภาพได้ดังนี้
deadlock

 

Priority Inversion คือเทรดที่มี priority สูงกว่า ไม่สามารถทำงานได้ เนื่องจากรอ resource จากเทรดต่ำกว่า
priority invertion

จากรูป T1 มี priority สูงที่สุดส่วน T3 มี priority ต่ำที่สุด เมื่อเริ่มโปรแกรม T3 ได้ทำงานก่อน จากนั้นก็เข้าใช้ resource A เมื่อโปรแรกทำงานได้ 1 วินาที T1 ก็ได้เข้ามายัง schduler ทำให้ schduler ต้องหยุดการทำงานของ T3 ไว้ก่อนเพราะ priority ของ T1 นั้นสูงกว่า T3 และ จากนั้นเมื่อถึงเวลา 1.5 วินาที T2 ก็ได้เข้ามาใน schdule แต่ยังไม่ได้สามารถทำงานได้เพราะ T1 ที่มี priory สูงกว่าทำงานอยู่ จนกระทั่งถึงวินาทีที่ 2 เทรด T1 ต้องการใช้ A แต่ไม่สามารถใช้ได้เนื่องจากถูก T3 ใช้งานอยู่ ทำให้เทรด T1 ต้องหยุดการทำงานไว้เพื่อรอ A จาก T3 แต่ที่แย่กว่านั้นคือ เนื่องจาก T2 มี priority ที่มากกว่า T3 จึงได้โอกาสทำงานก่อน T3 นั่นก็แปลว่า T1 ต้องรอให้ T2 ทำงานเสร็จก่อน ทั้งๆที่ T2 ไม่ได้มีส่วนเกี่ยวข้องกับ A เลย กลายเป็นต้องรอทั้ง T2 และ T3 เหตุการณ์ที่เกิดขึ้นจะเห็นว่าแม้ว่า T1 มี priority สูงกว่า T2 และ T3 แต่เทรดที่มี priority ต่ำกว่าทั้งสองกลับได้ทำงานก่อน ทำให้การกำหนด priority ของเทรดไม่เป็นไปตามจุดประสงค์ที่ต้องการให้ priority สูงกว่าทำงานก่อนเสมอ

Starvation เกิดปัญหาเกี่ยวกับ priotity ของเทรดเช่นเดียวกัน สิ่งที่เกิดขึ้นคือเทรดที่มี priority ต่ำกว่า ไม่มีโอกาสได้เข้าใช้ resource เนื่องจากถูกเทรดที่มี priority สูงกว่าแย่งไปเสมอ

Thread safty

โค้ดที่เป็น thread safty คือโค้ดที่รับประกันว่า ในงานทำงานแบบ multithreading ถ้าหากมีข้อมูลที่ใช้ร่วมกันระหว่างเทรด ข้อมูลส่วนนี้จะสามารถใช้งานได้พร้อมกันทุกๆเทรด  การที่จะทำให้โค้ด thread-safe นั้นสามารถทำได้หลายวิธี เช่นการใช้ Mutex ดังตัวอย่างที่ผ่านมา หรือการใช้อ็อบเจ็กที่เป็น immutable และใน Foundation Class มีคลาสทั้งที่เป็นแบบ thread safe  และ thread-unsafe เช่น คลาสตัวอย่างตามตารางต่อไปนี้

thread_safty

อย่างไรก็ถามถึงแม้ว่าอ็อบเจ็กจะเป็น thread-unsafe แต่ถ้าเทรดเข้าใช้งานทีละเทรด โค้ดก็ยังเป็น thread safe และในการเขียนโปรแกรมที่ใช้ UI ด้วยคลาสในกลุ่ม UIKit และ AppKit ควรหลีกเลี่ยงจากการปรับค่าต่างๆของ UI จาก background thread เนื่องจากคลาส UI เหล่านี้ไม่ได้เป็น thread-safe ยกตัวอย่างเพื่อให้เห็นภาพได้ง่ายขึ้น เช่น สมมติว่าเทรด background โหลดรูปภาพจากอินเตอร์เน็ต เมื่อโหลดเสร็จก็จะทำการเปลี่ยนภาพในตารางโดยทำงานภายใน background thread นั้นเลย และในขณะเดียว main thread ก็เปลี่ยนภาพใหม่จากรูปที่เราเลือกใน Photo Library ปัญหาที่อาจจะเกิดขึ้นคือเกิดการ release ภาพสองครั้ง เนื่องจาก backround thread ทำการ release ภาพเดิมเพื่อเปลี่ยนรูป แต่ยังไม่ทันจะได้เปลี่ยนภาพใหม่ schduler ก็สลับให้ main thread ทำงาน ดังนั้น main thread ก็จะ release ภาพเดิมเพื่อเปลี่ยนรูปเช่นกัน ทำให้โปรแกรมเกิดข้อผิดพลาดและปิดตัวลง ดังนั้นเมื่อไหร่ก็ตามที่ต้องเปลี่ยนค่าต่างๆของ UI ควรทำใน main thread เสมอ

Atomic property

ในบทที่ 7 เราได้พูดถึง atomic property ไปแล้ว ซึ่งพร๊อพเพอร์ตี้จะถูกกำหนดมาตั้งแต่แรกอยู่แล้วโดยที่เราไม่ต้องประกาศ และอย่างที่ได้กล่าวไปแล้วว่า atomic ไม่ได้หมายถึงการรับประกัน thread safe แต่จะรับประกันว่าเมื่อเราเรียก set หรือ get จะใช้เมธอดได้สมบูรณ์ เพื่อขยายความให้เข้าใจมากขึ้น ยกตัวอย่างเช่น เทรด A กำลังใช้งานอ็อบเจ็ก X โดยการเรียกเมธอด getX ในขณะเดียวกัน เทรด B และ C ก็เรียกเมธอด setX เพื่อเปลี่ยนค่า ถ้าหากเทรดทั้งสามทำงานพร้อมกัน การใช้ atomic จะรับประกันแค่เทรด A B C จะเรียกใช้ set/get ได้อย่างสมบูรณ์โดยไม่เกิดการ crash ( nonatomic จะเกิดการ crash ) แต่เทรด A อาจจะได้รับค่าใหม่จาก B หรือ C หรืออาจจะเป็นค่าเดิมก่อนที่เทรด B และ C จะเปลี่ยนค่าก็เป็นไปได้ เพราะไม่ได้รับประกัน thread safty นั่นเอง

Run loops

รันลูปคือโครงสร้างพื้นฐานที่ทำงานร่วมกับเทรด โดยมีหน้าที่เป็นลูปประมวลผลเหตุการณ์ (processing event loop) เช่น เมื่อผู้ใช้งานกดลงที่หน้าจอสัมผัสของ iPhone โปรแกรมก็จะส่ง event เข้ามาที่รันลูป
run loop

การทำงานของ run loop แสดงดังภาพ โดย event ของ run loops นั้นจะรับเข้ามาสองทางคือ input ซึ่งจะมีลักษณะเป็น asynchronous event โดยทั่วๆไปเหตุการณ์นี้จะได้รับจากเทรดอื่นๆหรือจากโปรเซสอื่น เช่น การกดปุ่มบน keyboard , หรือเมื่อ network ได้รับข้อมูล ส่วนทางที่สองคือผ่านทาง timer มีลักษณะเป็น synchronous event มักจะได้รับจากส่ง message ในเทรดเดียวกัน จุดประสงค์หลักของรันลูปคือทำให้เทรดไม่อยู่เฉย (busy) เมื่อต้องทำงาน และให้เทรดหลับ (sleep) เมื่อไม่ต้องทำอะไร โดยทั่วๆไปเราไม่ต้องเข้าไปเกี่ยวข้องโดยตรงกับ run loop แต่เนื่องจาก Application ที่เราเขียนกันในหนังสือเล่มนี้ไม่ใช่ iOS , Mac Application แต่เป็นแบบ Console ซึ่งไม่มี GUI ดังนั้นแล้วหาก Application ของเราต้องการ run loop เพื่อที่ใช้จัดช่วยการ event ต่างๆ เช่น เขียนโปรแกรม server เพื่อติดต่อกับ client หรือโปรแกรมมีการเรียกใช้ timer เราจำเป็นต้องสร้าง run loop ขึ้นมาเอง การจัดการ run loop สามารถทำได้ด้วยการใช้คลาส NSRunLoop เราจะลองเขียนโปรแกรมที่ง่ายๆ สักโปรแกรม

Program 14.8
Graphic.h

Graphic.m

Graphic ทำหน้าที่แค่แสดงผลผ่าน console อย่างเดียว ส่วนโปรแกรมหลักก็สร้างเทรดโดยกำหนดให้เมธอด draw: ทำงาน

main.m

Program 14.8 Output

Draw on: Graphic Thread
(=^-ω-^=)

หลังจากให้โปรแกรมทำงานก็จะได้ผลลัพธ์ตามที่แสดง แต่สิ่งที่เกิดขึ้นตามมากก็คือ โปรแกรมไม่หยุดทำงาน นั่นเป็นเพราะว่าเมื่อให้ run loop ทำงานด้วยเมธอด run ก็จะทำงานไปเรื่อยๆ ในกรณีของโปรแกรมแบบ GUI เราสามารถปิดโปรแกรมได้จากเมนู quite หรือกดปุ่ม home ในกรณีที่เป็น iOS แต่โปรแกรมที่เขียนกันอยู่นี้เป็นแบบ console application เราจึงต้องเขียนโค้ดเพื่อช่วยให้โปรแกรมจบการทำงานให้หลังจากแสดงข้อความเสร็จ โดยการแก้ไขโค้ดส่วน run loop ให้เป็นดังนี้

สิ่งที่เราได้เปลี่ยนแปลงคือ ประกาศอ็อบเจ็ก NSDate โดยใช้คลาสเมธอด distantPast ซึ่งเราจะได้เวลาในอดีตที่ห่างไกลจากปัจจุบันมากๆ จากนั้นโค้ดในส่วนลูปอ็อบเจ็ก runLoop เรียกเมธอด runMode:beforeDate: โดยส่งพารามิเตอร์คือ NSDefaultRunLoopMode เพื่อเป็นการกำหนดให้ run loop ทำงานในแบบทั่วๆไป ( รายละเอียดการทำงานแบบอื่นศึกษาได้จาก document โดยตรง ) ส่วนพารามิเตอร์ที่สองเป็นการกำหนด limit date การทำงานของเมธอดนี้จะให้รันลูปทำงาน 1 ครั้ง และตรวจสอบว่าถึงเวลาที่กำหนดไว้หรือยัง หากเกินเวลาที่กำหนดก็จะส่งค่า YES กลับมา และเมื่อพิจารณาจากโค้ดจะเห็นว่า date ที่เราได้ส่งให้กับเมธอด เป็นเวลาอดีตที่ไกลมากๆ ดังนั้นเวลาก็จะเกินที่กำหนดเสมอ หรือพูดอีกอย่างว่าโค้ดส่วนนี้จะส่งค่า YES กลับมาเสมอ ฉะนั้นแล้วตัวแปรสำคัญที่ทำให้ลูป while นี้จบการทำงานจริงๆคือพร็อพเพอร์ตี้ isFinished ของอ็อบเจ็ก graphic น่ันเอง

Perform method on thread

จากปัญหา thread safty ที่เพิ่งจะได้กล่าวไปว่า เราควรจะให้เทรดที่ทำงานเกี่ยวข้องกับ UI ทำงานที่ main thread เสมอ วิธีการที่จะกำหนดให้อ็อบเจ็กทำงานที่ main thread , background thread หรือ thread เทรดใดๆ สามารถทำได้ด้วยการใช้เมธอด performSelectorOnMainThread:withObject:waitUntilDone: และเมธอดอื่นในประเภทเดียวกัน เช่น เมธอด performSelector:onThread:withObject:waitUntilDone: เป็นต้น  เราจะเขียนโปรแกรมเพื่อจำลองการทำงานในระบบ GUI โดยเราจะเพิ่มคลาส Network เข้ามาในโปรแกรมเพื่อจำลองการเชื่อมต่ออินเตอร์เน็ต และใช้คลาส Graphic เพื่อจำลองการวาดภาพที่หน้าจอ

Program 14.9
Network.h

Network.m

คลาส Network เมื่อได้ข้อมูลแล้วก็จะส่งต่อไปยัง delegate ซึ่งในที่นี้จะเป็น instance ของคลาส Graphic และอย่างที่ได้กล่าวไปว่า การประมวลผลทาง UI ใดๆควรทำที่ main thread ดังนั้นแล้ว เราจึงบังคับให้ delegate เรียกเมธอด draw: ที่ main thread และให้รอจนกว่าการทำงานจะจบลง

main.m

ส่วนของ main program เราได้ประกาศอ็อบเจ็ก graphic ให้เป็น delegate ให้กับ network และสร้าง networkThread ขึ้นมาทำงาน จากนั้นก็ตั้งชื่อให้กับเทรดว่า “Network Thread” และเพื่อจะให้แน่ใจว่าเมธอดที่เราต้องการทำงานที่ main thread จริงหรือไม่ เราจึงได้กำหนดชื่อของ main thread ว่า “Main Thread” เมื่อโปรแกรมทำงานก็จะได้ผลลัพธ์ดังนี้
Program 14.9 Output

Draw on: Main Thread
(=^-ω-^=)

และจากผลลัพธ์แสดงให้เห็นว่า เมธอด draw: ของอ็อบเจ็ก graphic นั้นทำงานที่ main thread ไม่ได้ทำงานที่ network thread ถึงแม้ว่าโปรแกรมที่ได้เขียนไป เป็นเพียงโปรแกรมจำลอง แต่ผู้อ่านก็สามารนำไปประยุกต์ใช้งานได้จริงเช่น หากเปลี่ยนคลาส Graphic เป็นคลาสตาราง UITableView ของ iOS เราก็สามารถที่จะกำหนดให้ UITableView เรียก reloadData เพื่อปรับข้อมูลที่ main thread ได้ ดังเช่นโค้ดตัวอย่าง

โปรแกรมที่เราได้เขียนไปจำเป็นมีโค้ด run loop เนื่องจากเมธอด performSelector… นั้นทำงานผ่าน run loop และถูกเรียกในเทรด Network ซึ่งมี run loop แยกจาก main thread ถ้าหากเราเอาโค้ดส่วนที่เป็น run loop ออกจาก main thread เมธอด performSelectorOnMainThread… จะไม่ทำงาน แต่ถ้าเรียก performSelectorOnMainThread… ใน main thread เราไม่จำเป็นต้องเขียน run loop ก็สามารถทำงานได้เลย หรือถ้าหากเรียก performSelector.. และกำหนดให้ทำงานในเทรดที่เป็นคนเรียกก็จะทำงานได้เลยเช่นกัน ดังโค้ดตัวอย่างต่อไปนี้

Timer

คลาส timer เป็นคลาสที่สามารถตั้งเวลาการทำงานตามที่กำหนดได้ การทำงานของ timer จะทำงานร่วมกันกับ run loop โดยเมื่อสร้าง timer ก็จะถูกผูกเข้ากับ run loop เมื่อถึงเวลาที่กำหนด run loop ก็จะเป็นคนสั่งให้ timer ทำงาน หากมีการตั้งค่าให้ทำซ้ำ ก็จะปล่อย timer รอบใหม่และวนอย่างนี้ไปเรื่อยๆ เราจะเขียนโปรแกรมโดยใช้คลาส NSTimer กันอย่างง่ายสักโปรแกรม โดยปรับโค้ดของโปรแกรมที่แล้วใหม่ ดังนี้

Program 14.10
main.m

Program 14.10 Output

Draw on:
(=^-ω-^=)

โปรแกรมได้ตั้งเวลาให้อ็อบเจ็ก network โดยกำหนดว่าเมื่อเวลาผ่านไป 3 วินาทีให้เรียกเมธอด receiveData และเมื่อสั่งให้ทำงาน ผลลัพธ์จะแสดงหลังจากเริ่มโปรแกรมไปแล้ว 3 วินาที ตามที่เราได้กำหนดไว้ เนื่องจากเราได้กำหนดให้ NSTimer ทำงานที่ main thread เราจึงไม่ต้องกำหนด run loop ให้กับ timer แต่ตัวอย่างที่เราจะเขียนต่อไป เราจะใช้ timer จากเทรดอื่น

Program 14.11
Network.h

Network.m

เราได้เปลี่ยนคลาส Network เดิมให้มีเมธอด start เพื่อใช้ในการเริ่มการทำงานของ timer ซึ่งตัว timer นี้จะเรียกเมธอด updateData ทุกๆ 1 วินาที และให้ทำงานซ้ำเดิมไปเรื่อยๆ ด้วยการกำหนดพารามิเตอร์ repeats: เป็น YES หลังจากประกาศ timer เสร็จ เราได้เพิ่มโค้ดการทำงานของ run loop เข้ามา

เนื่องจาก timer ต้องการ run loop ในการทำงาน แต่โปรแแกรมที่เรากำลังเขียนนี้  NSTimer จะถูกสร้างในเทรดอื่นที่ไม่ใช่ main thread ดังนั้นแล้ว เราจึงต้องกำหนด run loop ให้กับ timer ไม่เช่นนั้น timer จะไม่ทำงาน นอกจากเมธอด start แล้วยังมีอีก 2 เมธอดคือ updateData และ doneUpdateData โดยเมธอดแรกนั้น ใช่เพื่อจำลองว่ามีข้อมูลจาก network ก็จะเอาข้อมูลที่ได้รับมานั้น เก็บไว้ที่ buffer หลังจากได้ข้อมูลครบถ้วน ก็จะหยุด timer และเรียก doneUpdateData

ในส่วนโค้ดของ main.m ใช้โค้ดเดิมของโปรแกรม 14.9 และแก้ไข thread ให้เริ่มที่เมธอด start

main.m

เมื่อให้โปรแกรมทำงาน ก็จะได้ผลดังนี้

Program 14.11 Output

Draw on: Main Thread
(=^-ω-^=)
(,,#゚Д゚)
(>д<)

โปรแกรมที่ได้เขียนก็ทำงานได้อย่างสมบูรณ์ จากที่เราได้กำหนดให้ timer ของเราทำงานทุกๆ 1 วินาที  และเรามีข้อมูลในอาร์เรย์ 3 ชุด ดังนั้นโปรแกรมของเราก็จะใช้เวลาประมาณ 3 วินาทีถึงจะเห็นผลลัพธ์ นอกจากการเรียกใช้คลาส NSTimer โดยตรงเรายังสามารถใช้เมธอด performSector:WithObject:afterDelay: ซึ่งเป็นส่วนเพิ่มเติมของ NSObject ได้อีกทางด้วย

Summary

ในบทนี้เราได้ทำความเข้าใจเกี่ยวกับการทำงานแบบ multithreading และปัญหาต่างๆที่ต้องระวังเมื่อเขียนโปรแกรม เช่น race condition นอกจากนี้เรายังได้เรียนรู้เกี่ยวกับ run loop และการทำงานของ timer
การทำงานแบบ multithread เป็นส่วนสำคัญอย่างมากของโปรแกรม ถึงแม้ว่าเราอาจจะไม่ได้เขียนโค้ด thread โดยตรง แต่การทำงานเบื้องหลังของ library หรือ framework ที่เราต้องใช้ในโปรแกรมก็อาจจะมีส่วนเกี่ยวข้องไม่ทางใดก็ทางหนึ่ง ดังนั้นการเขียนโปรแกรม iOS และ Mac OS X จำเป็นต้องมีความรู้ความเข้าใจเกี่ยวกับ Concurrent Programming เป็นอย่างมาก หนังสือเล่มนี้ไม่อาจจะอธิบายสิ่งต่างๆเกี่ยวกับ Concurrent Programming ได้หมด จึงแนะนำให้ผู้อ่านหาความรู้เพิ่มเติมนอกเหนือจากหนังสือเล่มนี้ หากไม่เข้าใจเนื้อหาในบทนี้ แนะนำให้อ่านทำความเข้าใจอีกรอบ เพราะเนื้อหาในบทต่อไปอย่าง Objective-C Block และ Grand Central Dispatch จำเป็นต้องเข้าใจเนื้อหาในบทนี้

 

โหลดแบบ PDF ไปอ่านได้ Chapter 14

และ source code โหลดได้ที่ github

Leave a Reply