อันดับแรกสิ่งสำคัญคือต้องทราบความแตกต่างระหว่างเธรดและคิวและสิ่งที่ GCD ทำจริงๆ เมื่อเราใช้คิวการจัดส่ง (ผ่าน GCD) เรากำลังเข้าคิวจริงๆไม่ใช่การจัดเรียงเธรด เฟรมเวิร์ก Dispatch ได้รับการออกแบบมาโดยเฉพาะเพื่อให้เราห่างจากเธรดเนื่องจาก Apple ยอมรับว่า "การใช้โซลูชันเธรดที่ถูกต้อง [สามารถ] กลายเป็นเรื่องยากมากหากไม่ [บางครั้ง] ไม่สามารถทำได้" ดังนั้นในการทำงานไปพร้อม ๆ กัน (งานที่เราไม่ต้องการให้ UI ค้าง) สิ่งที่เราต้องทำคือสร้างคิวของงานเหล่านั้นและส่งไปที่ GCD และ GCD จะจัดการเธรดที่เกี่ยวข้องทั้งหมด ดังนั้นสิ่งที่เราทำจริงๆคือการเข้าคิว
สิ่งที่สองที่ต้องรู้ทันทีคืองานคืออะไร งานคือรหัสทั้งหมดภายในบล็อกคิวนั้น (ไม่ใช่ภายในคิวเนื่องจากเราสามารถเพิ่มสิ่งต่างๆลงในคิวได้ตลอดเวลา แต่อยู่ในช่วงปิดที่เราเพิ่มลงในคิว) งานบางครั้งเรียกว่าบล็อกและบางครั้งเรียกบล็อกว่างาน (แต่มักเรียกกันว่างานโดยเฉพาะในชุมชน Swift) และไม่ว่ารหัสจะมากหรือน้อยรหัสทั้งหมดภายในวงเล็บปีกกาจะถือเป็นงานเดียว:
serialQueue.async {
// this is one task
// it can be any number of lines with any number of methods
}
serialQueue.async {
// this is another task added to the same queue
// this queue now has two tasks
}
และเห็นได้ชัดว่าการกล่าวถึงพร้อมกันนั้นหมายถึงในเวลาเดียวกันกับสิ่งอื่น ๆ และอนุกรมหมายถึงหนึ่งต่อจากนั้น ในการจัดลำดับบางสิ่งบางอย่างหรือการใส่สิ่งที่เป็นอนุกรมเพียงแค่หมายถึงการดำเนินการตั้งแต่ต้นจนจบตามลำดับจากซ้ายไปขวาบนลงล่างโดยไม่หยุดชะงัก
มีสองประเภทของคิวอนุกรมและพร้อมกันมีแต่คิวทั้งหมดจะพร้อมกันเมื่อเทียบกับกันและกัน ข้อเท็จจริงที่ว่าคุณต้องการเรียกใช้โค้ดใด ๆ "ในพื้นหลัง" หมายความว่าคุณต้องการเรียกใช้พร้อมกับเธรดอื่น (โดยปกติจะเป็นเธรดหลัก) ดังนั้นทุกคิวจัดส่งแบบอนุกรมหรือพร้อมกันดำเนินงานของพวกเขาไปพร้อม ๆ กันเมื่อเทียบกับการรอคิวอื่น ๆ การทำให้เป็นอนุกรมใด ๆ ที่ดำเนินการโดยคิว (โดยคิวอนุกรม) จะเกี่ยวข้องกับงานภายในคิวการจัดส่ง [อนุกรม] เดียวเท่านั้น (เช่นในตัวอย่างด้านบนซึ่งมีงานสองงานอยู่ในคิวอนุกรมเดียวกันงานเหล่านั้นจะถูกดำเนินการทีละรายการ อื่น ๆ ไม่เคยพร้อมกัน)
SERIAL QUEUES (มักเรียกว่าคิวการจัดส่งส่วนตัว) รับประกันการดำเนินการทีละงานตั้งแต่ต้นจนจบตามลำดับที่เพิ่มลงในคิวเฉพาะนั้น นี่เป็นการรับประกันเฉพาะการทำให้เป็นอนุกรมที่ใดก็ได้ในการอภิปรายเกี่ยวกับคิวการจัดส่ง- ว่างานเฉพาะภายในคิวอนุกรมที่เฉพาะเจาะจงจะถูกดำเนินการแบบอนุกรม อย่างไรก็ตามคิวอนุกรมสามารถรันพร้อมกันกับคิวอนุกรมอื่น ๆ ได้หากเป็นคิวที่แยกจากกันเพราะอีกครั้งคิวทั้งหมดจะพร้อมกันโดยสัมพันธ์กัน งานทั้งหมดรันบนเธรดที่แตกต่างกัน แต่ไม่ใช่ทุกงานที่รับประกันว่าจะรันบนเธรดเดียวกัน (ไม่สำคัญ แต่น่ารู้) และกรอบงาน iOS ไม่ได้มาพร้อมกับคิวอนุกรมที่พร้อมใช้งานคุณต้องสร้างมันขึ้นมา คิวส่วนตัว (ไม่ใช่ส่วนกลาง) เป็นแบบอนุกรมโดยค่าเริ่มต้นดังนั้นในการสร้างคิวแบบอนุกรม:
let serialQueue = DispatchQueue(label: "serial")
คุณสามารถทำให้พร้อมกันผ่านคุณสมบัติแอตทริบิวต์:
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])
แต่ ณ จุดนี้หากคุณไม่ได้เพิ่มแอตทริบิวต์อื่น ๆ ลงในคิวส่วนตัว Apple ขอแนะนำให้คุณใช้หนึ่งในคิวส่วนกลางที่พร้อมใช้งาน (ซึ่งทั้งหมดจะเกิดขึ้นพร้อมกัน) ที่ด้านล่างของคำตอบนี้คุณจะเห็นวิธีอื่นในการสร้างคิวอนุกรม (โดยใช้คุณสมบัติเป้าหมาย) ซึ่งเป็นวิธีที่ Apple แนะนำให้ทำ (เพื่อการจัดการทรัพยากรที่มีประสิทธิภาพมากขึ้น) แต่สำหรับตอนนี้การติดฉลากก็เพียงพอแล้ว
CONCURRENT QUEUES (มักเรียกกันว่าคิวการจัดส่งทั่วโลก) สามารถดำเนินงานพร้อมกันได้ อย่างไรก็ตามงานนี้รับประกันได้ว่าจะเริ่มต้นตามลำดับที่เพิ่มลงในคิวนั้น ๆ แต่ต่างจากคิวอนุกรมคือคิวไม่รอให้งานแรกเสร็จสิ้นก่อนที่จะเริ่มงานที่สอง งาน (เช่นเดียวกับคิวอนุกรม) ทำงานบนเธรดที่แตกต่างกันและ (เช่นเดียวกับคิวอนุกรม) ไม่ใช่ทุกงานที่รับประกันว่าจะรันบนเธรดเดียวกัน (ไม่สำคัญ แต่น่าสนใจที่จะรู้) และกรอบงาน iOS มาพร้อมกับสี่คิวที่พร้อมใช้งานพร้อมกัน คุณสามารถสร้างคิวพร้อมกันได้โดยใช้ตัวอย่างข้างต้นหรือโดยใช้หนึ่งในคิวทั่วโลกของ Apple (ซึ่งโดยปกติจะแนะนำ):
let concurrentQueue = DispatchQueue.global(qos: .default)
ความต้านทานต่อวงจรรีเทน: คิวการจัดส่งเป็นอ็อบเจ็กต์ที่มีการอ้างอิง แต่คุณไม่จำเป็นต้องเก็บรักษาและปล่อยคิวส่วนกลางเนื่องจากเป็นแบบโกลบอลดังนั้นการเก็บรักษาและการปล่อยจะถูกละเว้น คุณสามารถเข้าถึงคิวส่วนกลางได้โดยตรงโดยไม่ต้องกำหนดให้กับพร็อพเพอร์ตี้
มีสองวิธีในการจัดส่งคิว: พร้อมกันและแบบอะซิงโครนัส
SYNC DISPATCHINGหมายความว่าเธรดที่จัดส่งคิว (เธรดการเรียก) หยุดชั่วคราวหลังจากจัดส่งคิวและรอให้งานในบล็อกคิวนั้นเสร็จสิ้นก่อนดำเนินการต่อ ในการจัดส่งพร้อมกัน:
DispatchQueue.global(qos: .default).sync {
// task goes in here
}
ASYNC DISPATCHINGหมายความว่าเธรดการเรียกยังคงทำงานต่อไปหลังจากจัดส่งคิวและไม่รอให้งานในบล็อกคิวนั้นดำเนินการเสร็จสิ้น ในการจัดส่งแบบอะซิงโครนัส:
DispatchQueue.global(qos: .default).async {
// task goes in here
}
ตอนนี้อาจมีคนคิดว่าในการดำเนินการงานแบบอนุกรมควรใช้คิวอนุกรมและไม่ถูกต้อง ในการดำเนินการหลาย ๆงานในอนุกรมควรใช้คิวอนุกรม แต่งานทั้งหมด (แยกด้วยตัวเอง) จะดำเนินการแบบอนุกรม ลองพิจารณาตัวอย่างนี้:
whichQueueShouldIUse.syncOrAsync {
for i in 1...10 {
print(i)
}
for i in 1...10 {
print(i + 100)
}
for i in 1...10 {
print(i + 1000)
}
}
ไม่ว่าคุณจะกำหนดค่าอย่างไร (อนุกรมหรือพร้อมกัน) หรือจัดส่ง (ซิงค์หรือ async) คิวนี้งานนี้จะดำเนินการแบบอนุกรมเสมอ ลูปที่สามจะไม่ทำงานก่อนลูปที่สองและลูปที่สองจะไม่ทำงานก่อนลูปแรก นี่เป็นจริงในคิวใด ๆ โดยใช้การจัดส่งใด ๆ เป็นเวลาที่คุณแนะนำงานและ / หรือคิวหลายอย่างที่อนุกรมและการทำงานพร้อมกันเข้ามามีบทบาทจริงๆ
พิจารณาสองคิวนี้หนึ่งลำดับและหนึ่งพร้อมกัน:
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)
สมมติว่าเราจัดส่งสองคิวพร้อมกันใน async:
concurrentQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
103
3
104
4
105
5
เอาต์พุตของพวกเขาสับสน (ตามที่คาดไว้) แต่สังเกตว่าแต่ละคิวดำเนินการงานของตัวเองแบบอนุกรม นี่เป็นตัวอย่างพื้นฐานที่สุดของการทำงานพร้อมกัน - สองงานที่ทำงานพร้อมกันในพื้นหลังในคิวเดียวกัน ตอนนี้มาสร้างซีเรียลแรก:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
101
1
2
102
3
103
4
104
5
105
คิวแรกไม่ควรดำเนินการแบบอนุกรมใช่หรือไม่? มันเป็น (และเป็นครั้งที่สอง) สิ่งอื่นที่เกิดขึ้นในเบื้องหลังก็ไม่น่ากังวลใด ๆ กับคิว เราบอกให้ซีเรียลคิวรันเป็นอนุกรมและมันก็ทำ ... แต่เราให้มันแค่งานเดียว ตอนนี้ให้มันสองงาน:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
และนี่คือตัวอย่างขั้นพื้นฐานที่สุด (และเป็นไปได้เท่านั้น) ของการทำให้เป็นอนุกรม - สองงานที่ทำงานแบบอนุกรม (หนึ่งงานต่อกัน) ในพื้นหลัง (ไปยังเธรดหลัก) ในคิวเดียวกัน แต่ถ้าเราทำให้พวกเขาสองคิวอนุกรมแยกกัน (เนื่องจากในตัวอย่างข้างต้นเป็นคิวเดียวกัน) เอาต์พุตของพวกเขาจะสับสนอีกครั้ง:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue2.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
3
103
4
104
5
105
และนี่คือสิ่งที่ฉันหมายถึงเมื่อฉันบอกว่าคิวทั้งหมดสัมพันธ์กัน นี่คือคิวอนุกรมสองคิวที่ดำเนินการงานพร้อมกัน (เนื่องจากเป็นคิวแยกกัน) คิวไม่รู้จักหรือสนใจคิวอื่น ตอนนี้ให้กลับไปที่คิวอนุกรมสองคิว (ของคิวเดียวกัน) และเพิ่มคิวที่สามซึ่งเป็นคิวพร้อมกัน:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 1000)
}
}
1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005
เป็นเรื่องที่ไม่คาดคิดทำไมคิวพร้อมกันจึงรอให้คิวอนุกรมเสร็จสิ้นก่อนที่จะดำเนินการ นั่นไม่ใช่การเกิดขึ้นพร้อมกัน สนามเด็กเล่นของคุณอาจแสดงผลลัพธ์ที่แตกต่างออกไป แต่ของฉันแสดงให้เห็น และมันแสดงให้เห็นสิ่งนี้เนื่องจากลำดับความสำคัญของคิวพร้อมกันของฉันไม่สูงพอที่ GCD จะทำงานได้เร็วกว่านั้น ดังนั้นหากฉันให้ทุกอย่างเหมือนเดิม แต่เปลี่ยน QoS ของคิวทั่วโลก (คุณภาพการบริการซึ่งเป็นเพียงระดับความสำคัญของคิว) let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
ผลลัพธ์จะเป็นไปตามที่คาดไว้:
1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105
คิวอนุกรมทั้งสองดำเนินการงานของพวกเขาในอนุกรม (ตามที่คาดไว้) และคิวที่ทำงานพร้อมกันทำงานได้เร็วขึ้นเนื่องจากได้รับลำดับความสำคัญสูง (QoS สูงหรือคุณภาพของบริการ)
คิวสองคิวพร้อมกันเช่นในตัวอย่างการพิมพ์ครั้งแรกของเราแสดงงานพิมพ์ที่สับสน (ตามที่คาดไว้) เพื่อให้พวกเขาในการพิมพ์อย่างเรียบร้อยในอนุกรมเราจะต้องทำทั้งสองของพวกเขาคิวเดียวกันอนุกรม(อินสแตนซ์เดียวกันของคิวที่เป็นอย่างดีไม่ได้เป็นเพียงป้ายเดียว) จากนั้นแต่ละงานจะถูกดำเนินการตามลำดับเมื่อเทียบกับงานอื่น ๆ อย่างไรก็ตามอีกวิธีหนึ่งในการทำให้พวกเขาพิมพ์เป็นอนุกรมคือให้ทั้งสองอย่างพร้อมกัน แต่เปลี่ยนวิธีการจัดส่ง:
concurrentQueue.sync {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
โปรดจำไว้ว่าการจัดส่งการซิงค์หมายความว่าเธรดการโทรจะรอจนกว่างานในคิวจะเสร็จสมบูรณ์ก่อนดำเนินการต่อ ข้อแม้ที่เห็นได้ชัดคือเธรดการโทรจะถูกหยุดจนกว่างานแรกจะเสร็จสมบูรณ์ซึ่งอาจเป็นหรือไม่ใช่วิธีที่คุณต้องการให้ UI ทำงาน
และด้วยเหตุนี้เราจึงไม่สามารถทำสิ่งต่อไปนี้ได้:
DispatchQueue.main.sync { ... }
นี่เป็นการรวมคิวและวิธีการจัดส่งที่เป็นไปได้เพียงวิธีเดียวที่เราไม่สามารถดำเนินการได้นั่นคือการจัดส่งแบบซิงโครนัสบนคิวหลัก และนั่นเป็นเพราะเรากำลังขอให้คิวหลักหยุดจนกว่าเราจะดำเนินการงานภายในวงเล็บปีกกา ... ซึ่งเราส่งไปยังคิวหลักซึ่งเราเพียงแค่แช่แข็ง นี้เรียกว่าการหยุดชะงัก หากต้องการดูการใช้งานจริงในสนามเด็กเล่น:
DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock
สิ่งสุดท้ายที่จะกล่าวถึงคือทรัพยากร เมื่อเราให้คิวงาน GCD จะค้นหาคิวที่พร้อมใช้งานจากพูลที่จัดการภายใน เท่าที่เขียนคำตอบนี้มี 64 คิวต่อ qos อาจดูเหมือนมาก แต่สามารถใช้งานได้อย่างรวดเร็วโดยเฉพาะอย่างยิ่งโดยไลบรารีของบุคคลที่สามโดยเฉพาะกรอบฐานข้อมูล ด้วยเหตุนี้ Apple จึงมีคำแนะนำเกี่ยวกับการจัดการคิว (ระบุไว้ในลิงค์ด้านล่าง) ความเป็นหนึ่ง:
แทนที่จะสร้างคิวการทำงานพร้อมกันแบบส่วนตัวให้ส่งงานไปยังหนึ่งในคิวการจัดส่งพร้อมกันทั่วโลก สำหรับงานอนุกรมกำหนดเป้าหมายของคิวอนุกรมของคุณเป็นหนึ่งในคิวที่ทำงานพร้อมกันทั่วโลก
ด้วยวิธีนี้คุณสามารถรักษาลักษณะการทำงานที่ต่อเนื่องกันของคิวในขณะที่ลดจำนวนคิวที่แยกจากกันในการสร้างเธรด
ในการทำสิ่งนี้แทนที่จะสร้างขึ้นเหมือนที่เราเคยทำมาก่อน (ซึ่งคุณยังทำได้) Apple ขอแนะนำให้สร้างอนุกรมคิวแบบนี้:
let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))
สำหรับการอ่านเพิ่มเติมฉันขอแนะนำสิ่งต่อไปนี้:
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue