Computer >> คอมพิวเตอร์ >  >> สมาร์ทโฟน >> iPhone

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

การทำงานพร้อมกันใน iOS เป็นหัวข้อใหญ่ ดังนั้นในบทความนี้ ฉันต้องการขยายในหัวข้อย่อยเกี่ยวกับคิวและเฟรมเวิร์ก Grand Central Dispatch (GCD)

โดยเฉพาะอย่างยิ่ง ฉันต้องการสำรวจความแตกต่างระหว่างคิวแบบอนุกรมและแบบพร้อมกัน ตลอดจนความแตกต่างระหว่างการดำเนินการแบบซิงโครนัสและแบบอะซิงโครนัส

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

ฉันสร้างแอพคู่หู SwiftUI เพื่อสาธิตแนวคิดในบทความนี้ด้วยสายตา แอพนี้ยังมีแบบทดสอบสั้นๆ สนุกๆ ที่ฉันแนะนำให้คุณลองอ่านก่อนและหลังอ่านบทความนี้ ดาวน์โหลดซอร์สโค้ดที่นี่ หรือรับเบต้าสาธารณะที่นี่

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

แนะนำตัว

เริ่มต้นด้วยการแนะนำสั้น ๆ เกี่ยวกับ GCD และคิวการส่ง ข้ามไปที่ Sync vs Async . ได้ตามสบาย ส่วนถ้าคุณคุ้นเคยกับหัวข้อนี้แล้ว

Concurrency และ Grand Central Dispatch

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

Grand Central Dispatch สร้างขึ้นโดย Apple เมื่อกว่า 10 ปีที่แล้วเพื่อเป็นนามธรรมเพื่อช่วยให้นักพัฒนาเขียนโค้ดแบบมัลติเธรดโดยไม่ต้องสร้างและจัดการเธรดด้วยตนเอง

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

ข้อได้เปรียบที่สำคัญของ GCD คือคุณไม่ต้องกังวลกับทรัพยากรฮาร์ดแวร์ขณะเขียนโค้ดพร้อมกัน GCD จัดการเธรดพูลให้คุณ และจะปรับขนาดจาก Apple Watch แบบคอร์เดียวไปจนถึง MacBook Pro แบบมัลติคอร์

ส่งคิว

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

โดยทั่วไปแล้ว คุณมีคิวอยู่สามประเภท:

  • คิวการจัดส่งหลัก (แบบอนุกรม กำหนดไว้ล่วงหน้า)
  • คิวส่วนกลาง (กำหนดไว้ล่วงหน้าพร้อมกัน)
  • คิวส่วนตัว (อาจเป็นแบบอนุกรมหรือพร้อมกัน คุณสร้างขึ้นเองได้)

ทุกแอพมาพร้อม Main Queue ซึ่งเป็น serial คิวที่รันงานบนเธรดหลัก คิวนี้มีหน้าที่ในการวาด UI ของแอปพลิเคชันของคุณและตอบสนองต่อการโต้ตอบของผู้ใช้ (แตะ เลื่อน เลื่อน ฯลฯ) หากคุณบล็อกคิวนี้นานเกินไป แอป iOS ของคุณจะหยุดทำงาน และแอป macOS ของคุณจะแสดงชายหาดที่น่าอับอาย ลูก/ล้อหมุน

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

URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        DispatchQueue.main.async { // UI work
            self.label.text = String(data: data, encoding: .utf8)
        }
    }
}
URLSession ส่งการเรียกกลับบนคิวพื้นหลัง

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

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

ตัวอย่างเช่น นี่คือรหัสสำหรับส่งงานแบบอะซิงโครนัสไปยัง การโต้ตอบของผู้ใช้ (ลำดับความสำคัญสูงสุด) คิว QoS:

DispatchQueue.global(qos: .userInteractive).async {
    print("We're on a global concurrent queue!") 
}

หรือคุณสามารถเรียก ลำดับความสำคัญเริ่มต้น คิวโกลบอลโดยไม่ระบุ QoS แบบนี้:

DispatchQueue.global().async {
    print("Generic global queue")
}
QoS เริ่มต้นอยู่ระหว่าง เริ่มต้นโดยผู้ใช้ และ อรรถประโยชน์

นอกจากนี้ คุณสามารถสร้างคิวส่วนตัวของคุณเองโดยใช้ไวยากรณ์ต่อไปนี้:

let serial = DispatchQueue(label: "com.besher.serial-queue")
serial.async {
    print("Private serial queue")
}
คิวส่วนตัวเป็นแบบอนุกรมโดยค่าเริ่มต้น

เมื่อสร้างคิวส่วนตัว การใช้ป้ายกำกับที่สื่อความหมาย (เช่น สัญกรณ์ DNS แบบย้อนกลับ) จะช่วยได้มาก เนื่องจากจะช่วยคุณในขณะแก้ไขจุดบกพร่องในเนวิเกเตอร์, lldb และเครื่องมือของ Xcode:

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

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

let concurrent = DispatchQueue(label: "com.besher.serial-queue", attributes: .concurrent)
concurrent.sync {
    print("Private concurrent queue")
}

มีพารามิเตอร์ QoS เสริมเช่นกัน คิวส่วนตัวที่คุณสร้างในท้ายที่สุดจะลงจอดในคิวพร้อมกันทั่วโลกตามพารามิเตอร์ที่กำหนด

งานมีอะไรบ้าง

ฉันพูดถึงการส่งงานไปยังคิว งานสามารถอ้างถึงกลุ่มของรหัสใด ๆ ที่คุณส่งไปยังคิวโดยใช้ sync หรือ async ฟังก์ชั่น. สามารถส่งในรูปแบบของการปิดโดยไม่ระบุชื่อ:

DispatchQueue.global().async {
    print("Anonymous closure")
}

หรือภายในรายการงานจัดส่งที่ดำเนินการในภายหลัง:

let item = DispatchWorkItem(qos: .utility) {
    print("Work item to be executed later")
}
สังเกตว่าเรากำหนด QoS ของงานอย่างไรที่นี่

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

ตัวอย่างเช่น หากคุณมี 3 ลูปภายใน เดียวกัน งาน ลูปเหล่านี้จะ เสมอ ดำเนินการตามลำดับ:

DispatchQueue.global().async {
    for i in 0..<10 {
        print(i)
    }

    for _ in 0..<10 {
        print("?")
    }

    for _ in 0..<10 {
        print("?")
    }
}

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

งานแต่ละงานสามารถมีระดับ QoS ของตัวเองได้เช่นกัน (โดยค่าเริ่มต้น งานจะใช้ลำดับความสำคัญของคิว) ความแตกต่างระหว่าง QoS ของคิวและ QoS ของงานนำไปสู่พฤติกรรมที่น่าสนใจที่เราจะพูดถึงในส่วนการผกผันลำดับความสำคัญ

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

ซิงค์กับ Async

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

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

คิดถึง sync มีความหมายเหมือนกันกับ 'การปิดกั้น'

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

คิวปัจจุบัน?

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

ตัวอย่างเช่น หากคุณเรียก sync . ของคุณ คำสั่งภายใน viewDidLoad คิวปัจจุบันของคุณจะเป็นคิวการจัดส่งหลัก หากคุณเรียกใช้ฟังก์ชันเดียวกันภายในตัวจัดการการเสร็จสิ้น URLSession คิวปัจจุบันของคุณจะเป็นคิวเบื้องหลัง

กลับไปที่ sync vs async มาดูตัวอย่างกัน:

DispatchQueue.global().sync {
    print("Inside")
}
print("Outside")
// Console output:
// Inside
// Outside

โค้ดด้านบนจะบล็อกคิวปัจจุบัน เข้าสู่การปิดและรันโค้ดบนคิวส่วนกลางโดยพิมพ์ "Inside" ก่อนพิมพ์ "Outside" รับประกันคำสั่งซื้อนี้

มาดูกันว่าจะเกิดอะไรขึ้นถ้าเราลอง async แทน:

DispatchQueue.global().async {
    print("Inside")
}
print("Outside")
// Potential console output (based on QoS): 
// Outside
// Inside

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

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

ตัวอย่างเช่น เมื่อคุณส่งงานโดยใช้ sync , GCD เพิ่มประสิทธิภาพการทำงานโดยดำเนินการงานนั้นบนเธรดปัจจุบัน (ผู้โทร)

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
จาก Dispatcher บน Github

อันไหนน่าใช้

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

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

เพื่อแสดงแนวคิดนี้ ลองใช้ฟังก์ชันเล็กๆ ที่ยอมรับข้อมูลภาพ ทำการคำนวณราคาแพงเพื่อประมวลผลภาพ แล้วส่งคืนผลลัพธ์:

func processImage(data: Data) -> UIImage? {
    guard let image = UIImage(data: data) else { return nil }
    // calling an expensive function
    let processedImage = upscaleAndFilter(image: image)
    return processedImage 
}

ในตัวอย่างนี้ ฟังก์ชัน upscaleAndFilter(image:) อาจใช้เวลาหลายวินาที เราจึงต้องการถ่ายลงในคิวแยกต่างหากเพื่อหลีกเลี่ยงการหยุด UI มาสร้างคิวเฉพาะสำหรับการประมวลผลภาพ แล้วส่งฟังก์ชันราคาแพงแบบอะซิงโครนัส:

let imageProcessingQueue = DispatchQueue(label: "com.besher.image-processing")

func processImageAsync(data: Data) -> UIImage? {
    guard let image = UIImage(data: data) else { return nil }
    
    imageProcessingQueue.async {
        let processedImage = upscaleAndFilter(image: image)
        return processedImage
    }
}
โค้ดนี้ไม่สามารถคอมไพล์ได้!

รหัสนี้มีปัญหาสองประการ ขั้นแรก คำสั่ง return อยู่ภายในการปิดแบบ async ดังนั้นจึงไม่คืนค่าเป็น processImageAsync(data:) อีกต่อไป ฟังก์ชั่นและปัจจุบันไม่มีจุดประสงค์

แต่ปัญหาที่ใหญ่กว่าคือ processImageAsync(data:) . ของเรา ฟังก์ชั่นจะไม่คืนค่าใด ๆ อีกต่อไป เนื่องจากฟังก์ชั่นถึงจุดสิ้นสุดของร่างกายก่อนที่จะเข้าสู่ async ปิด

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

let imageProcessingQueue = DispatchQueue(label: "com.besher.image-processing")

func processImageAsync(data: Data, completion: @escaping (UIImage?) -> Void) {
    guard let image = UIImage(data: data) else {
        completion(nil)
        return
    }

    imageProcessingQueue.async {
        let processedImage =  self.upscaleAndFilter(image: image)
        completion(processedImage)
    }
}

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

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

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

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

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

กำลังจัดส่งในคิวเดียวกัน

แม้ว่าการส่งงานแบบอะซิงโครนัสจากคิวไปยังตัวเองจะปลอดภัย (เช่น คุณสามารถใช้ .asyncAfter ในคิวปัจจุบัน) คุณไม่สามารถส่งงาน พร้อมกัน จากคิวเข้าคิวเดียวกัน การทำเช่นนั้นจะส่งผลให้แอปหยุดชะงักทันที!

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

การบล็อกคิวหลัก

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
ต้องการใช้ async จากคิวหลัก

ซีเรียลเทียบกับพร้อมกัน

ซีเรียล และ พร้อมกัน ส่งผลต่อปลายทาง  —  คิวที่งานของคุณถูกส่งไปรัน ซึ่งตรงกันข้ามกับ ซิงค์ และ async ซึ่งส่งผลต่อ แหล่งที่มา .

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

นอกจากนี้ เมื่อคุณบล็อกคิวซีเรียล (โดยใช้ sync การโทร สัญญาณ หรือเครื่องมืออื่นๆ) การทำงานทั้งหมดในคิวนั้นจะหยุดจนกว่าการบล็อกจะสิ้นสุดลง

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
จาก Dispatcher บน Github

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

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
จาก Dispatcher บน Github

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

สิ่งสำคัญที่ควรทราบคือแนวคิดของ ซีเรียล เทียบกับ พร้อมกัน เกี่ยวข้องเฉพาะเมื่อกล่าวถึงคิวเฉพาะ คิวทั้งหมดสัมพันธ์กัน ซึ่งกันและกัน .

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

เพื่อแสดงให้เห็นถึงความพร้อมกันของหลายคิวอนุกรม มาดูตัวอย่างนี้:

let serial1 = DispatchQueue(label: "com.besher.serial1")
let serial2 = DispatchQueue(label: "com.besher.serial2")

serial1.async {
    for _ in 0..<5 { print("?") }
}

serial2.async {
    for _ in 0..<5 { print("?") }
}
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

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

หากเราต้องการให้แน่ใจว่าการวนรอบแรกเสร็จสิ้นก่อนเริ่มการวนรอบที่สอง เราสามารถส่งงานแรกพร้อมกันจากผู้โทรได้:

let serial1 = DispatchQueue(label: "com.besher.serial1")
let serial2 = DispatchQueue(label: "com.besher.serial2")

serial1.sync { // <---- we changed this to 'sync'
    for _ in 0..<5 { print("?") }
}
// we don't get here until first loop terminates
serial2.async {
    for _ in 0..<5 { print("?") }
}
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

สิ่งนี้ไม่จำเป็นเสมอไป เพราะตอนนี้เรากำลังบล็อกผู้โทรในขณะที่วงแรกกำลังทำงานอยู่

เพื่อหลีกเลี่ยงการบล็อกผู้โทร เราสามารถส่งงานทั้งสองแบบอะซิงโครนัส แต่ส่งไปยัง เดียวกัน คิวอนุกรม:

let serial = DispatchQueue(label: "com.besher.serial")

serial.async {
    for _ in 0..<5 { print("?") }
}

serial.async {
    for _ in 0..<5 { print("?") }
}	
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

ตอนนี้งานของเราดำเนินการไปพร้อมกับ ผู้โทร , ในขณะที่ยังรักษาคำสั่งของพวกเขาไว้เหมือนเดิม.

โปรดทราบว่าหากเราสร้างคิวเดี่ยวพร้อมกันผ่านพารามิเตอร์ทางเลือก เราจะกลับไปที่ผลลัพธ์ที่สับสนตามที่คาดไว้:

let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent)

concurrent.async {
    for _ in 0..<5 { print("?") }
}

concurrent.async {
    for _ in 0..<5 { print("?") }
}
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

บางครั้งคุณอาจสับสนระหว่างการดำเนินการแบบซิงโครนัสกับการดำเนินการแบบอนุกรม (อย่างน้อยฉันก็ทำ) แต่สิ่งเหล่านี้แตกต่างกันมาก ตัวอย่างเช่น ลองเปลี่ยนการส่งครั้งแรกในบรรทัดที่ 3 จากตัวอย่างก่อนหน้าของเราเป็น sync โทร:

let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent)

concurrent.sync {
    for _ in 0..<5 { print("?") }
}

concurrent.async {
    for _ in 0..<5 { print("?") }
}
สิ่งนี้อาจทำให้เข้าใจผิดได้
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

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

คำตอบคือ ไม่!

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

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

อันไหนน่าใช้

คิวอนุกรมใช้ประโยชน์จากการเพิ่มประสิทธิภาพ CPU และการแคช และช่วยลดการสลับบริบท

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

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

หลุมพราง

การผกผันลำดับความสำคัญและคุณภาพของบริการ

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

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

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

GCD แก้ไขการผกผันของลำดับความสำคัญโดยเพิ่ม QoS ของคิวชั่วคราวที่มีงานที่มีลำดับความสำคัญต่ำซึ่งอยู่ "ข้างหน้า" หรือปิดกั้นงานที่มีลำดับความสำคัญสูงของคุณ

เหมือนมีรถติดอยู่ข้างหน้า ของ รถพยาบาล. ทันใดนั้นพวกเขาก็ได้รับอนุญาตให้ข้ามไฟแดงเพียงเพื่อให้รถพยาบาลสามารถเคลื่อนที่ได้ (ในความเป็นจริงรถเคลื่อนไปด้านข้าง แต่ลองนึกภาพถนนแคบ ๆ (ต่อเนื่อง) หรืออะไรก็ตาม :-P)

เพื่อแสดงปัญหาการผกผัน ให้เริ่มด้วยรหัสนี้:


enum Color: String {
    case blue = "?"
    case white = "⚪️"
}

func output(color: Color, times: Int) {
    for _ in 1...times {
        print(color.rawValue)
    }
}

let starterQueue = DispatchQueue(label: "com.besher.starter", qos: .userInteractive)
let utilityQueue = DispatchQueue(label: "com.besher.utility", qos: .utility)
let backgroundQueue = DispatchQueue(label: "com.besher.background", qos: .background)
let count = 10

starterQueue.async {

    backgroundQueue.async {
        output(color: .white, times: count)
    }

    backgroundQueue.async {
        output(color: .white, times: count)
    }

    utilityQueue.async {
        output(color: .blue, times: count)
    }

    utilityQueue.async {
        output(color: .blue, times: count)
    }

    // next statement goes here
}

เราสร้างคิวเริ่มต้น (ที่เราส่งงาน จาก ) รวมถึงสองคิวที่มี QoS ต่างกัน จากนั้นเราส่งงานไปยังแต่ละคิวทั้งสองนี้ โดยแต่ละงานจะพิมพ์วงกลมที่มีสีเฉพาะจำนวนเท่ากัน (ยูทิลิตี้ คิว เป็นสีน้ำเงิน พื้นหลัง เป็นสีขาว)

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
ไม่แปลกใจเลย

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

// add this after the last async statement, 
// still inside starterQueue.async
backgroundQueue.sync {}
อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
การผกผันลำดับความสำคัญ

ผลลัพธ์ในคอนโซลพลิกคว่ำ! ตอนนี้ คิวที่มีลำดับความสำคัญสูงกว่า (ยูทิลิตี้) จะอยู่ท้ายสุดเสมอ และ 10–15 วงสุดท้ายจะเป็น สีน้ำเงิน

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

ในตัวอย่างข้างต้น ผู้โทร (starterQueue) มี QoS สูงสุด (userInteractive) ดังนั้น sync ที่ดูเหมือนไม่มีพิษมีภัย งานไม่เพียงแต่บล็อกคิวเริ่มต้นเท่านั้น แต่ยังทำงานบนเธรด QoS ระดับสูงของสตาร์ทเตอร์ด้วย งานจึงทำงานด้วย QoS สูง แต่มีงานอื่นอีกสองงานที่อยู่ข้างหน้าในคิวพื้นหลังเดียวกันที่มี พื้นหลัง QoS ตรวจพบการผกผันของลำดับความสำคัญ!

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

หมายเหตุด้านข้าง:หากคุณลบคิวเริ่มต้นออกจากตัวอย่างนั้นและส่งจากคิวหลักแทน คุณจะได้ผลลัพธ์ที่คล้ายคลึงกัน เนื่องจากคิวหลักยังมี การโต้ตอบของผู้ใช้ QoS.

เพื่อหลีกเลี่ยงการผกผันของลำดับความสำคัญในตัวอย่างนี้ เราต้องหลีกเลี่ยงการบล็อกคิวเริ่มต้นด้วย sync คำแถลง. ใช้ async จะแก้ปัญหานั้นได้

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

ด้ายระเบิด

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

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

สภาพการแข่งขัน

Swift Arrays, Dictionaries, Structs และประเภทค่าอื่นๆ จะไม่ปลอดภัยสำหรับเธรดตามค่าเริ่มต้น ตัวอย่างเช่น เมื่อคุณมีหลายเธรดที่พยายามเข้าถึงและแก้ไข อาร์เรย์เดียวกัน คุณจะเริ่มประสบปัญหา

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

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

เป็นปัญหาที่ยุ่งยากเพราะแอปของคุณสามารถทำงานได้ 10 ครั้งโดยไม่มีปัญหา จากนั้นจึงหยุดทำงานเป็นครั้งที่ 11 เครื่องมือหนึ่งที่มีประโยชน์มากสำหรับสถานการณ์นี้คือ Thread Sanitizer ใน Xcode การเปิดใช้งานตัวเลือกนี้จะช่วยให้คุณระบุสภาพการแข่งขันที่อาจเกิดขึ้นในแอปของคุณ

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด
tตัวเลือกของเขามีเฉพาะในเครื่องจำลองเท่านั้น

เพื่อแสดงปัญหา ลองมาดูตัวอย่างนี้ (ที่เป็นที่ยอมรับ)

class ViewController: UIViewController {
    
    let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent)
    var array = [1,2,3,4,5]

    override func viewDidLoad() {
        for _ in 0...1 {
            race()
        }
    }

    func race() {

        concurrent.async {
            for i in self.array { // read access
                print(i)
            }
        }

        concurrent.async {
            for i in 0..<10 {
                self.array.append(i) // write access
            }
        }
    }
}

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

อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

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

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

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

เมื่อเสร็จแล้ว ภารโรงจะเอาป้าย (สิ่งกีดขวาง) เพื่อให้คนที่ต่อคิวอยู่ข้างนอกเข้าไปได้ในที่สุด

นี่คือสิ่งที่ดูเหมือนในโค้ด:

class ViewController: UIViewController {
    let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent)
    let isolation = DispatchQueue(label: "com.besher.isolation", attributes: .concurrent)
    private var _array = [1,2,3,4,5]
    
    var threadSafeArray: [Int] {
        get {
            return isolation.sync {
                _array
            }
        }
        set {
            isolation.async(flags: .barrier) {
                self._array = newValue
            }
        }
    }
    
    override func viewDidLoad() {
        for _ in 0...15 {
            race()
        }
    }
    
    func race() {
        concurrent.async {
            for i in self.threadSafeArray {
                print(i)
            }
        }
        
        concurrent.async {
            for i in 0..<10 {
                self.threadSafeArray.append(i)
            }
        }
    }
}

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

ผู้รับต้องเป็น sync เพื่อส่งกลับค่าโดยตรง ตัวตั้งค่าสามารถเป็น async เนื่องจากเราไม่จำเป็นต้องบล็อกผู้โทรในขณะที่กำลังเขียน

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

บทสรุป

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

สรุป

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

ฉันชอบคำอุปมาจากประกาศเกี่ยวกับการทำงานพร้อมกันอย่างรวดเร็วของ Swift ที่มี 'เกาะแห่งการทำให้เป็นอันดับในทะเลของการทำงานพร้อมกัน' ความรู้สึกนี้ถูกแบ่งปันในทวีตนี้โดย Matt Diephouse:

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

หากคุณมีคำถามหรือความคิดเห็น โปรดติดต่อฉันได้ที่ Twitter

เบเชอร์ อัล มาเลห์

ภาพปกโดย Onur K บน Unsplash

ดาวน์โหลดแอปที่แสดงร่วมที่นี่:

แอป almaleh/DispatcherCompanion สำหรับบทความของฉันเกี่ยวกับการทำงานพร้อมกัน มีส่วนร่วมในการพัฒนา almaleh/Dispatcher โดยการสร้างบัญชีบน GitHub อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด almalehGitHub อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Fireworks — โปรแกรมแก้ไขภาพอนุภาคสำหรับ SwiftGenerate Swift code ได้ทันทีสำหรับ macOS และ iOS ในขณะที่คุณออกแบบและทำซ้ำกับเอฟเฟกต์อนุภาคของคุณ อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Besher Al MalehFlawless iOS อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด คุณไม่จำเป็นต้อง (เสมอ) ต้องการ [ตัวตนที่อ่อนแอ] ในบทความนี้ เราจะพูดถึงตัวตนที่อ่อนแอ ด้านในของฝาปิด Swift เพื่อหลีกเลี่ยงรอบการรักษา &สำรวจกรณีที่อาจจำเป็นหรือไม่จำเป็นต้องจับภาพตัวเองอย่างอ่อนแอ อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Besher Al MalehFlawless iOS อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

Further reading:

IntroductionExplains how to implement concurrent code paths in an application.Concurrent Programming:APIs and Challenges · objc.ioobjc.io publishes books on advanced techniques and practices for iOS and OS X development อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Florian Kugler อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Low-Level Concurrency APIs · objc.ioobjc.io publishes books on advanced techniques and practices for iOS and OS X development อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Daniel Eggert

https://khanlou.com/2016/04/the-GCD-handbook/

Concurrent vs serial queues in GCDI’m struggling to fully understand the concurrent and serial queues in GCD. I have some issues and hoping someone can answer me clearly and at the point.I’m reading that serial queues are created... อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Bogdan AlexandruStack Overflow อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด

WWDC Videos:

Modernizing Grand Central Dispatch Usage - WWDC 2017 - Videos - Apple DevelopermacOS 10.13 and iOS 11 have reinvented how Grand Central Dispatch and the Darwin kernel collaborate, enabling your applications to run... อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Apple Developer อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Building Responsive and Efficient Apps with GCD - WWDC 2015 - Videos - Apple DeveloperwatchOS and iOS Multitasking place increased demands on your application’s efficiency and responsiveness. With expert guidance from the... อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด Apple Developer อธิบายการทำงานพร้อมกัน:วิธีสร้างแอป iOS แบบมัลติเธรด