พร้อมกันกับคิวอนุกรมใน GCD


117

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

  1. ฉันกำลังอ่านว่ามีการสร้างและใช้คิวอนุกรมเพื่อดำเนินการทีละงาน อย่างไรก็ตามจะเกิดอะไรขึ้นหาก:

    • ฉันสร้างคิวอนุกรม
    • ฉันใช้dispatch_async(ในคิวอนุกรมที่ฉันเพิ่งสร้างขึ้น) สามครั้งเพื่อส่งสามบล็อก A, B, C

    สามบล็อกจะดำเนินการ:

    • ตามลำดับ A, B, C เนื่องจากคิวเป็นแบบอนุกรม

      หรือ

    • ในเวลาเดียวกัน (ในเวลาเดียวกันกับเธรดพาร์ราเลล) เนื่องจากฉันใช้การจัดส่ง ASYNC
  2. ฉันกำลังอ่านว่าฉันสามารถใช้dispatch_syncกับคิวพร้อมกันเพื่อดำเนินการบล็อกทีละรายการ ในกรณีนี้ทำไมจึงมีคิวแบบอนุกรมเนื่องจากฉันสามารถใช้คิวพร้อมกันได้ตลอดเวลาซึ่งฉันสามารถส่ง SYNCHRONOUSLY บล็อกได้มากเท่าที่ฉันต้องการ

    ขอบคุณสำหรับคำอธิบายที่ดี!


ที่ดีจำเป็นต้องมีคำถามง่ายๆซิงค์ส่ง VS async
น้ำผึ้ง

คำตอบ:


216

ตัวอย่างง่ายๆ: คุณมีบล็อกที่ใช้เวลาหนึ่งนาทีในการดำเนินการ คุณเพิ่มลงในคิวจากเธรดหลัก ลองดูสี่กรณี

  • async - พร้อมกัน: โค้ดทำงานบนเธรดพื้นหลัง การควบคุมกลับไปที่เธรดหลักทันที (และ UI) บล็อกไม่สามารถสันนิษฐานได้ว่าเป็นบล็อกเดียวที่รันบนคิวนั้น
  • async - serial: โค้ดทำงานบนเธรดพื้นหลัง การควบคุมกลับไปที่เธรดหลักทันที บล็อกสามารถสันนิษฐานได้ว่าเป็นบล็อกเดียวที่รันบนคิวนั้น
  • การซิงค์ - พร้อมกัน: โค้ดทำงานบนเธรดพื้นหลัง แต่เธรดหลักรอให้เสร็จสิ้นบล็อกการอัปเดตใด ๆ ของ UI บล็อกไม่สามารถสันนิษฐานได้ว่าเป็นบล็อกเดียวที่ทำงานบนคิวนั้น (ฉันสามารถเพิ่มบล็อกอื่นโดยใช้ async ได้ไม่กี่วินาทีก่อนหน้านี้)
  • การซิงค์ - อนุกรม: รหัสทำงานบนเธรดพื้นหลัง แต่เธรดหลักรอให้เสร็จสิ้นบล็อกการอัปเดตใด ๆ ของ UI บล็อกสามารถสันนิษฐานได้ว่าเป็นบล็อกเดียวที่รันบนคิวนั้น

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


14
คุณกำลังบอกฉันว่า: (1) ประเภทของคิว (conc หรืออนุกรม) เป็นองค์ประกอบเดียวที่ตัดสินว่างานจะดำเนินการตามลำดับหรือในพาราเลล ;; (2) ประเภทการจัดส่ง (ซิงค์หรือ async) เพียงบอกว่าการดำเนินการไปหรือไม่ไปที่คำสั่งถัดไป? ฉันหมายความว่าถ้าฉันส่งงาน SYNC รหัสจะบล็อกจนกว่างานนั้นจะเสร็จสิ้นไม่ว่าจะดำเนินการในคิวใด?
Bogdan Alexandru

13
@BogdanAlexandru ถูกต้อง. คิวกำหนดนโยบายการดำเนินการไม่ใช่วิธีการจัดคิวบล็อก การซิงค์รอให้บล็อกเสร็จสิ้น async ไม่ทำ
Jano

2
@swiftBUTCHER ถึงจุดหนึ่งใช่ เมื่อคุณสร้างคิวคุณสามารถระบุจำนวนเธรดสูงสุดได้ หากคุณเพิ่มงานน้อยกว่าที่จะดำเนินการควบคู่กัน ยิ่งไปกว่านั้นงานบางอย่างจะยังคงอยู่ในคิวจนกว่าจะมีความสามารถเพียงพอ
Stephen Darlington

2
@PabloA เธรดหลักเป็นคิวแบบอนุกรมดังนั้นจึงมีเพียงสองกรณีเท่านั้น นอกเหนือจากนั้นมันเหมือนกันทุกประการ Async ส่งคืนทันที (และบล็อกอาจถูกดำเนินการเมื่อสิ้นสุดลูปการรันปัจจุบัน) gotcha หลักคือถ้าคุณซิงค์จากเธรดหลักไปยังเธรดหลักซึ่งในกรณีนี้คุณจะเกิดการชะงักงัน
Stephen Darlington

1
@ShauketSheikh เลขที่เธรดหลักเป็นคิวแบบอนุกรม แต่ไม่ใช่คิวอนุกรมทั้งหมดที่เป็นเธรดหลัก ในจุดที่สี่เธรดหลักจะปิดกั้นรอให้เธรดอื่นแข่งขันกัน หากคิวอนุกรมเป็นเธรดหลักคุณจะพบการชะงักงัน
Stephen Darlington

122

นี่คือคู่ของการทดลองที่ฉันได้ทำเพื่อให้ฉันเข้าใจเกี่ยวกับเหล่านี้serial, คิวด้วยconcurrentGrand Central Dispatch

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

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

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

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

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

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

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

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

นี่คือบทสรุปของการทดลองเหล่านี้

อย่าลืมว่าคุณใช้ GCD เพียงแค่เพิ่มงานในคิวและทำงานจากคิวนั้น คิวจัดส่งงานของคุณทั้งในเธรดหลักหรือพื้นหลังขึ้นอยู่กับว่าการดำเนินการเป็นแบบซิงโครนัสหรืออะซิงโครนัส ประเภทของคิว ได้แก่ Serial, Concurrent, Main dispatch Queue งานทั้งหมดที่คุณดำเนินการจะเสร็จสิ้นตามค่าเริ่มต้นจากคิวการจัดส่งหลักมีคิวทั่วโลกที่กำหนดไว้ล่วงหน้าสี่คิวสำหรับแอปพลิเคชันของคุณที่จะใช้และหนึ่งคิวหลัก (DispatchQueue.main) ยังสามารถสร้างคิวของคุณเองและดำเนินงานจากคิวนั้นได้ด้วยตนเอง

งานที่เกี่ยวข้องกับ UI ควรดำเนินการจากเธรดหลักเสมอโดยการส่งงานไปยังคิวหลักยูทิลิตี้แบบสั้นคือDispatchQueue.main.sync/asyncในขณะที่การดำเนินการที่เกี่ยวข้องกับเครือข่าย / งานหนักควรทำแบบอะซิงโครนัสเสมอไม่ว่าเธรดใดที่คุณใช้ทั้ง main หรือ background

แก้ไข: อย่างไรก็ตามมีหลายกรณีที่คุณต้องดำเนินการเรียกเครือข่ายแบบซิงโครนัสในเธรดพื้นหลังโดยไม่ต้องแช่แข็ง UI (เช่นการรีเฟรชโทเค็น OAuth และรอว่าจะสำเร็จหรือไม่) คุณต้องรวมวิธีการนั้นไว้ในการดำเนินการแบบอะซิงโครนัสวิธีนี้หนักของคุณ การดำเนินการจะดำเนินการตามลำดับและไม่มีการบล็อกเธรดหลัก

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

แก้ไขแก้ไข:คุณสามารถดูวิดีโอสาธิตได้ที่นี่


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

@ คนขี้เกียจ iOS คนนั้น웃ฉันยังไม่เข้าใจความแตกต่างระหว่าง async พร้อมกันและอนุกรม async ความหมายของการใช้อย่างใดอย่างหนึ่งคืออะไร ทั้งคู่ทำงานในพื้นหลังโดยไม่รบกวน UI แล้วทำไมคุณถึงใช้การซิงค์? ไม่ใช่การซิงค์รหัสทั้งหมด หนึ่งสิ่งหลังจากสิ่งอื่น?
eonist

1
@GitSyncApp คุณสามารถดูวิดีโอได้ที่นี่
Anish Parajuli 웃

@ คนขี้เกียจ iOS คนนั้น웃: ขอบคุณสำหรับการทำเช่นนั้น ฉันโพสต์บน slack swift-lang จะเป็น👌ถ้าคุณสามารถสร้างเกี่ยวกับ DispatchGroup และ DispatchWorkItem ได้เช่นกัน : D
eonist

ฉันได้ทดสอบครั้งสุดท้ายของคุณฟังก์ชันconcurrentQueue.syncของdoLongSyncTaskInConcurrentQueue()มันพิมพ์เธรดหลักTask will run in different threadดูเหมือนจะไม่เป็นความจริง
gabbler

54

อันดับแรกสิ่งสำคัญคือต้องทราบความแตกต่างระหว่างเธรดและคิวและสิ่งที่ 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


7

ถ้าผมเข้าใจอย่างถูกต้องเกี่ยวกับการทำงาน GCD ผมคิดว่ามีสองประเภทDispatchQueue, serialและconcurrentในเวลาเดียวกันมีสองทางวิธีการDispatchQueueส่งงานของตนที่ได้รับมอบหมายclosureหนึ่งแรกคือasyncและอื่น ๆ syncที่มี สิ่งเหล่านี้ร่วมกันกำหนดวิธีดำเนินการปิด (งาน) จริง

ฉันพบว่าserialและconcurrentหมายถึงจำนวนเธรดที่คิวสามารถใช้ได้serialหมายถึงหนึ่งในขณะที่concurrentหมายถึงจำนวนมาก และsyncและasyncหมายถึงงานที่จะดำเนินการที่ด้ายด้ายที่โทรมาหรือด้ายพื้นฐานคิวว่าsyncวิธีการทำงานในหัวข้อที่โทรมาในขณะที่asyncวิธีการทำงานบนพื้นฐานด้าย

ต่อไปนี้เป็นโค้ดทดลองที่สามารถทำงานบน Xcode playground

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

หวังว่าจะเป็นประโยชน์


7

ฉันชอบที่จะคิดว่าสิ่งนี้โดยใช้คำอุปมานี้ (นี่คือลิงค์ไปยังภาพต้นฉบับ):

พ่อคงต้องการความช่วยเหลือ

ลองจินตนาการว่าพ่อของคุณกำลังทำอาหารและคุณมีโซดาสักแก้ว คุณเอาแก้วไปให้พ่อทำความสะอาดโดยวางไว้ข้างๆจานอื่น ๆ

ตอนนี้คุณพ่อของคุณคือการทำอาหารด้วยตัวเองดังนั้นเขาจะต้องทำพวกเขาหนึ่งโดยหนึ่ง: พ่อของคุณที่นี่หมายถึงคิวอนุกรม

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

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

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

หวังว่านี่จะช่วยได้


3

1. ฉันกำลังอ่านว่ามีการสร้างและใช้คิวอนุกรมเพื่อดำเนินการทีละงาน อย่างไรก็ตามจะเกิดอะไรขึ้นถ้า: - •ฉันสร้างคิวอนุกรม•ฉันใช้ dispatch_async (บนคิวอนุกรมที่ฉันเพิ่งสร้าง) สามครั้งเพื่อส่งบล็อก A, B, C สามครั้ง

คำตอบ : - บล็อกทั้งสามดำเนินการทีละบล็อกฉันได้สร้างโค้ดตัวอย่างหนึ่งรายการที่ช่วยให้เข้าใจ

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}

//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.