Đồng thời so với hàng đợi nối tiếp trong GCD


117

Tôi đang đấu tranh để hiểu đầy đủ các hàng đợi đồng thời và nối tiếp trong GCD. Tôi có một số vấn đề và hy vọng ai đó có thể trả lời tôi rõ ràng và tại điểm.

  1. Tôi đang đọc rằng các hàng đợi nối tiếp được tạo và sử dụng để thực hiện lần lượt từng nhiệm vụ. Tuy nhiên, điều gì xảy ra nếu:

    • Tôi tạo một hàng đợi nối tiếp
    • Tôi sử dụng dispatch_async(trên hàng đợi nối tiếp tôi vừa tạo) ba lần để gửi ba khối A, B, C

    Ba khối sẽ được thực hiện:

    • theo thứ tự A, B, C vì hàng đợi là nối tiếp

      HOẶC LÀ

    • đồng thời (cùng lúc trên các chủ đề parralel) vì tôi đã sử dụng công văn ASYNC
  2. Tôi đang đọc rằng tôi có thể sử dụng dispatch_synctrên các hàng đợi đồng thời để thực thi các khối lần lượt. Trong trường hợp đó, TẠI SAO các hàng đợi nối tiếp thậm chí còn tồn tại, vì tôi luôn có thể sử dụng một hàng đợi đồng thời trong đó tôi có thể gửi nhiều khối như tôi muốn?

    Cảm ơn cho bất kỳ lời giải thích tốt!


Một câu hỏi tiên quyết đơn giản tốt gửi đồng bộ hóa so với async
Honey

Câu trả lời:


216

Một ví dụ đơn giản: bạn có một khối mất một phút để thực thi. Bạn thêm nó vào hàng đợi từ luồng chính. Hãy xem xét bốn trường hợp.

  • async - đồng thời: mã chạy trên một luồng nền. Kiểm soát trả về ngay lập tức cho luồng chính (và UI). Khối không thể cho rằng đó là khối duy nhất chạy trên hàng đợi đó
  • async - serial: mã chạy trên một luồng nền. Kiểm soát trả về ngay lập tức cho chủ đề chính. Khối có thể cho rằng đó là khối duy nhất chạy trên hàng đợi đó
  • đồng bộ hóa - đồng thời: mã chạy trên một luồng nền nhưng luồng chính chờ nó kết thúc, chặn mọi cập nhật cho UI. Khối không thể cho rằng đó là khối duy nhất chạy trên hàng đợi đó (tôi có thể đã thêm một khối khác bằng cách sử dụng async vài giây trước đó)
  • sync - serial: mã chạy trên một luồng nền nhưng luồng chính chờ nó kết thúc, chặn mọi cập nhật cho UI. Khối có thể cho rằng đó là khối duy nhất chạy trên hàng đợi đó

Rõ ràng là bạn sẽ không sử dụng một trong hai cái cuối cùng cho các quy trình chạy dài. Bạn thường thấy nó khi bạn đang cố cập nhật giao diện người dùng (luôn ở luồng chính) từ thứ gì đó có thể đang chạy trên luồng khác.


14
Vì vậy, bạn đang nói với tôi rằng: (1) loại hàng đợi (conc hoặc serial) là phần tử DUY NHẤT quyết định liệu các tác vụ được thực hiện theo thứ tự hay song song ;; (2) loại công văn (đồng bộ hóa hoặc không đồng bộ) chỉ cho biết liệu việc thực thi có HOẶC không đi đến hướng dẫn tiếp theo không? Ý tôi là, nếu tôi gửi một nhiệm vụ SYNC, mã sẽ chặn cho đến khi các nhiệm vụ đó kết thúc, bất kể hàng đợi nó được thực hiện vào ngày nào?
Bogdan Alexandru

13
@BogdanAlexandru Đúng. Hàng đợi ra lệnh cho chính sách thực thi, không phải cách bạn xếp hàng khối. Đồng bộ hóa chờ khối hoàn tất, async không.
Jano

2
@swiftBUTCHER Đến một thời điểm nhất định, vâng. Khi bạn tạo một hàng đợi, bạn có thể chỉ định số lượng chủ đề tối đa. Nếu bạn thêm ít nhiệm vụ hơn thì chúng sẽ thực thi song song. Với nhiều hơn thế, một số nhiệm vụ sẽ vẫn ở trong hàng đợi cho đến khi có khả năng.
Stephen Darlington

2
@PabloA., Chủ đề chính là một hàng đợi nối tiếp nên chỉ thực sự có hai trường hợp. Ngoài ra, nó giống hệt nhau. Async trả về ngay lập tức (và khối có thể được thực thi ở cuối vòng lặp chạy hiện tại). Gotcha chính là nếu bạn đồng bộ hóa từ luồng chính sang luồng chính, trong trường hợp đó bạn sẽ gặp bế tắc.
Stephen Darlington

1
@ShauketSheikh Số Chủ đề chính là một hàng đợi nối tiếp, nhưng không phải tất cả các hàng đợi nối tiếp là chủ đề chính. Trong điểm thứ tư, luồng chính sẽ chặn, chờ đợi một luồng khác cạnh tranh công việc của nó. Nếu hàng đợi nối tiếp là luồng chính, bạn sẽ gặp bế tắc.
Stephen Darlington

122

Dưới đây là một vài thí nghiệm mà tôi đã thực hiện để khiến tôi hiểu về những điều này serial, concurrentxếp hàng với Grand 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")
        }
    }
}

Tác vụ sẽ chạy trong luồng khác nhau (trừ luồng chính) khi bạn sử dụng async trong GCD. Async có nghĩa là thực thi dòng tiếp theo, đừng đợi cho đến khi khối thực thi, kết quả là không chặn luồng chính & hàng đợi chính. Vì hàng đợi nối tiếp của nó, tất cả được thực hiện theo thứ tự chúng được thêm vào hàng đợi nối tiếp. Các tác vụ được thực thi ser seri luôn được thực thi một lần bởi một luồng duy nhất được liên kết với Hàng đợi.

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")
        }
    }
}

Tác vụ có thể chạy trong luồng chính khi bạn sử dụng đồng bộ hóa trong GCD. Đồng bộ hóa chạy một khối trên một hàng đợi nhất định và đợi nó hoàn thành, kết quả là chặn luồng chính hoặc hàng đợi chính. Vì vậy, hàng đợi chính cần đợi cho đến khi khối được gửi hoàn thành, luồng chính sẽ có sẵn để xử lý các khối từ hàng đợi khác ngoài hàng đợi chính. Do đó, có khả năng mã thực thi trên hàng đợi nền có thể thực sự được thực thi trên luồng chính Kể từ hàng đợi nối tiếp của nó, tất cả được thực hiện theo thứ tự chúng được thêm vào (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")
    }
}

Tác vụ sẽ chạy trong luồng nền khi bạn sử dụng async trong GCD. Async có nghĩa là thực thi dòng tiếp theo, đừng đợi cho đến khi khối thực thi, kết quả là không chặn luồng chính. Hãy nhớ trong hàng đợi đồng thời, tác vụ được xử lý theo thứ tự chúng được thêm vào hàng đợi nhưng với các luồng khác nhau được gắn vào hàng đợi. Hãy nhớ rằng họ không có nghĩa vụ phải hoàn thành nhiệm vụ như thứ tự họ được thêm vào hàng đợi. Trình đơn của nhiệm vụ khác nhau mỗi khi các luồng được tạo ra nhất thiết phải tự động. Các lệnh được thực hiện song song. Với nhiều hơn thế (maxConcienOperationCount) đạt được, một số tác vụ sẽ hoạt động như một chuỗi cho đến khi một luồng miễn phí.

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")
    }
}

Tác vụ có thể chạy trong luồng chính khi bạn sử dụng đồng bộ hóa trong GCD. Đồng bộ hóa chạy một khối trên một hàng đợi nhất định và đợi nó hoàn thành dẫn đến việc chặn luồng chính hoặc hàng đợi chính. Vì vậy, hàng đợi chính cần đợi cho đến khi khối được gửi hoàn thành, luồng chính sẽ có sẵn để xử lý các khối từ hàng đợi khác ngoài hàng đợi chính. Do đó, có khả năng mã thực thi trên hàng đợi nền có thể thực sự được thực thi trên luồng chính. Vì hàng đợi đồng thời của nó, các tác vụ có thể không hoàn thành theo thứ tự chúng được thêm vào hàng đợi. Nhưng với hoạt động đồng bộ, mặc dù chúng có thể được xử lý bởi các luồng khác nhau. Vì vậy, nó hoạt động như đây là hàng đợi nối tiếp.

Dưới đây là một bản tóm tắt của những thí nghiệm này

Hãy nhớ sử dụng GCD, bạn chỉ thêm tác vụ vào Hàng đợi và thực hiện tác vụ từ hàng đợi đó. Hàng đợi gửi nhiệm vụ của bạn trong luồng chính hoặc luồng tùy thuộc vào hoạt động là đồng bộ hay không đồng bộ. Các loại hàng đợi là Hàng đợi, Đồng thời, Hàng đợi công văn chính. Tất cả các tác vụ bạn thực hiện được thực hiện theo mặc định từ Hàng đợi công văn chính. Đã có bốn hàng đợi đồng thời toàn cầu được xác định trước cho ứng dụng của bạn và một hàng đợi chính (DispatchQueue.main). cũng có thể tự tạo hàng đợi của riêng bạn và thực hiện nhiệm vụ từ hàng đợi đó.

Giao diện người dùng Nhiệm vụ liên quan phải luôn được thực hiện từ luồng chính bằng cách gửi tác vụ đến Hàng đợi chính. Tiện ích tay nhanh là DispatchQueue.main.sync/async trong khi các hoạt động nặng / liên quan đến mạng phải luôn được thực hiện không đồng bộ, không có vấn đề gì khi bạn sử dụng luồng chính hoặc nền

EDIT: Tuy nhiên, có những trường hợp bạn cần thực hiện các hoạt động cuộc gọi mạng một cách đồng bộ trong một luồng nền mà không bị đóng băng UI (ví dụ: Mã thông báo OAuth và chờ đợi nếu nó thành công hay không). Bạn cần phải bọc phương thức đó trong một hoạt động không đồng bộ. các hoạt động được thực hiện theo thứ tự và không chặn luồng chính.

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")
        }
    }
}

EDIT EDIT: Bạn có thể xem video demo tại đây


Trình diễn tuyệt vời .... dòng tiếp theo không đợi cho đến khi khối thực thi kết quả không chặn luồng chính, đây là lý do tại sao nếu bạn sử dụng điểm dừng trên luồng nền thì nó sẽ nhảy đến }vì nó thực sự không thực thi tại thời điểm đó
Honey

@ Kẻ lười biếng iOS 웃 Tôi vẫn không hiểu sự khác biệt giữa nối tiếp async và nối tiếp async. Ý nghĩa của việc sử dụng một trong hai. Cả hai đều chạy trong nền không làm phiền UI. Và tại sao bạn sẽ sử dụng đồng bộ hóa? Không phải tất cả đồng bộ mã. cái này sau cái kia?
eonist

1
@GitSyncApp bạn có thể xem video tại đây
Anish Parajuli

@ Chàng trai iOS lười biếng đó: thx vì đã làm điều đó. Tôi đăng trên slack-lang. Sẽ là 👌 Nếu bạn có thể tạo một cái về Dispatchgroup và DispatchWorkItem. : D
eonist

Tôi đã thử nghiệm cuối cùng của bạn, concurrentQueue.synccủa doLongSyncTaskInConcurrentQueue()chức năng, nó in thread chính, Task will run in different threaddường như không đúng sự thật.
gabbler

53

Đầu tiên, điều quan trọng là phải biết sự khác biệt giữa các luồng và hàng đợi và GCD thực sự làm gì. Khi chúng tôi sử dụng hàng đợi công văn (thông qua GCD), chúng tôi thực sự xếp hàng, không phân luồng. Khung công tác được thiết kế đặc biệt để giúp chúng tôi tránh khỏi việc phân luồng, vì Apple thừa nhận rằng "việc thực hiện một giải pháp phân luồng chính xác [có thể] trở nên cực kỳ khó khăn, nếu không [đôi khi] không thể đạt được." Do đó, để thực hiện đồng thời các nhiệm vụ (các nhiệm vụ mà chúng ta không muốn đóng băng UI), tất cả những gì chúng ta cần làm là tạo một hàng đợi các nhiệm vụ đó và giao cho GCD. Và GCD xử lý tất cả các luồng liên quan. Do đó, tất cả những gì chúng tôi thực sự đang làm là xếp hàng.

Điều thứ hai cần biết ngay là nhiệm vụ là gì. Một tác vụ là tất cả các mã trong khối hàng đợi đó (không phải trong hàng đợi, bởi vì chúng ta có thể thêm mọi thứ vào hàng đợi mọi lúc, nhưng trong bao đóng nơi chúng ta đã thêm nó vào hàng đợi). Một nhiệm vụ đôi khi được gọi là một khối và một khối đôi khi được gọi là một nhiệm vụ (nhưng chúng thường được gọi là các nhiệm vụ, đặc biệt là trong cộng đồng Swift). Và cho dù nhiều hay ít mã, tất cả các mã trong dấu ngoặc nhọn đều được coi là một tác vụ duy nhất:

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
}

Và rõ ràng đề cập rằng đồng thời đơn giản có nghĩa là cùng một lúc với những thứ khác và nối tiếp có nghĩa là cái khác (không bao giờ cùng một lúc). Để nối tiếp một cái gì đó, hoặc đặt một cái gì đó nối tiếp, chỉ có nghĩa là thực hiện nó từ đầu đến cuối theo thứ tự từ trái sang phải, từ trên xuống dưới, không bị gián đoạn.

Có hai loại hàng đợi, nối tiếp và đồng thời, nhưng tất cả các hàng đợi là đồng thời liên quan đến nhau . Thực tế là bạn muốn chạy bất kỳ mã nào "trong nền" có nghĩa là bạn muốn chạy nó đồng thời với một luồng khác (thường là luồng chính). Do đó, tất cả các hàng đợi gửi, nối tiếp hoặc đồng thời, thực hiện các nhiệm vụ của chúng đồng thời liên quan đến các hàng đợi khác . Bất kỳ tuần tự hóa nào được thực hiện bởi hàng đợi (theo hàng đợi nối tiếp), chỉ phải thực hiện với các tác vụ trong hàng đợi gửi [nối tiếp] đó (như trong ví dụ ở trên có hai nhiệm vụ trong cùng một hàng đợi nối tiếp; các tác vụ đó sẽ được thực hiện sau cái khác, không bao giờ đồng thời).

NHIỆM VỤ SERIAL (thường được gọi là hàng đợi gửi riêng) đảm bảo thực hiện từng nhiệm vụ một từ lúc bắt đầu đến khi kết thúc theo thứ tự mà chúng đã được thêm vào hàng đợi cụ thể đó. Đây là bảo đảm duy nhất của việc xê-ri hóa ở bất cứ đâu trong cuộc thảo luận về hàng đợi - đó là các nhiệm vụ cụ thể trong một hàng đợi nối tiếp cụ thể được thực hiện nối tiếp. Tuy nhiên, hàng đợi nối tiếp có thể chạy đồng thời với các hàng đợi nối tiếp khác nếu chúng là các hàng đợi riêng biệt bởi vì, một lần nữa, tất cả các hàng đợi đồng thời liên quan đến nhau. Tất cả các tác vụ chạy trên các luồng riêng biệt nhưng không phải mọi tác vụ đều được đảm bảo để chạy trên cùng một luồng (không quan trọng, nhưng thú vị để biết). Và khung iOS không đi kèm với bất kỳ hàng đợi nối tiếp sẵn sàng sử dụng nào, bạn phải tạo chúng. Hàng đợi riêng tư (không toàn cầu) được nối tiếp theo mặc định, do đó, để tạo một hàng đợi nối tiếp:

let serialQueue = DispatchQueue(label: "serial")

Bạn có thể làm cho nó đồng thời thông qua thuộc tính thuộc tính của nó:

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

Nhưng tại thời điểm này, nếu bạn không thêm bất kỳ thuộc tính nào khác vào hàng đợi riêng tư, Apple khuyên bạn chỉ nên sử dụng một trong các hàng đợi toàn cầu sẵn sàng hoạt động của họ (tất cả đều đồng thời). Ở dưới cùng của câu trả lời này, bạn sẽ thấy một cách khác để tạo hàng đợi nối tiếp (sử dụng thuộc tính đích), đó là cách Apple khuyên bạn nên làm điều đó (để quản lý tài nguyên hiệu quả hơn). Nhưng bây giờ, ghi nhãn nó là đủ.

QUEUES CONCURRENT (thường được gọi là hàng đợi công văn toàn cầu) có thể thực hiện các nhiệm vụ cùng một lúc; tuy nhiên, các nhiệm vụ được đảm bảo bắt đầu theo thứ tự chúng đã được thêm vào hàng đợi cụ thể đó, nhưng không giống như các hàng đợi nối tiếp, hàng đợi không đợi nhiệm vụ đầu tiên kết thúc trước khi bắt đầu nhiệm vụ thứ hai. Các tác vụ (như với hàng đợi nối tiếp) chạy trên các luồng riêng biệt và (như với hàng đợi nối tiếp) không phải mọi tác vụ đều được đảm bảo để chạy trên cùng một luồng (không quan trọng, nhưng thú vị để biết). Và khung iOS đi kèm với bốn hàng đợi đồng thời sẵn sàng sử dụng. Bạn có thể tạo một hàng đợi đồng thời bằng ví dụ trên hoặc bằng cách sử dụng một trong các hàng đợi toàn cầu của Apple (thường được khuyến nghị):

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT: Các hàng đợi điều phối là các đối tượng được tính tham chiếu nhưng bạn không cần giữ lại và giải phóng hàng đợi toàn cầu vì chúng là toàn cục, do đó giữ lại và phát hành bị bỏ qua. Bạn có thể truy cập hàng đợi toàn cầu trực tiếp mà không cần phải gán chúng cho một tài sản.

Có hai cách để gửi hàng đợi: đồng bộ và không đồng bộ.

TRANH CHẤP SYNC có nghĩa là luồng trong đó hàng đợi được gửi đi (luồng gọi) tạm dừng sau khi gửi hàng đợi và đợi tác vụ trong khối hàng đợi đó kết thúc thực hiện trước khi tiếp tục. Để gửi đồng bộ:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

TRANH CHẤP ASYNC có nghĩa là luồng gọi tiếp tục chạy sau khi gửi hàng đợi và không đợi tác vụ trong khối hàng đợi đó hoàn tất thực hiện. Để gửi không đồng bộ:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

Bây giờ người ta có thể nghĩ rằng để thực hiện một nhiệm vụ nối tiếp, nên sử dụng hàng đợi nối tiếp và điều đó không chính xác. Để thực thi nhiều tác vụ nối tiếp, nên sử dụng hàng đợi nối tiếp, nhưng tất cả các tác vụ (tự cách ly) được thực hiện nối tiếp. Xem xét ví dụ này:

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)
    }
}

Bất kể bạn định cấu hình như thế nào (nối tiếp hoặc đồng thời) hoặc gửi (đồng bộ hóa hoặc không đồng bộ) hàng đợi này, tác vụ này sẽ luôn được thực hiện nối tiếp. Vòng lặp thứ ba sẽ không bao giờ chạy trước vòng lặp thứ hai và vòng lặp thứ hai sẽ không bao giờ chạy trước vòng lặp thứ nhất. Điều này đúng trong bất kỳ hàng đợi sử dụng bất kỳ công văn. Đó là khi bạn giới thiệu nhiều nhiệm vụ và / hoặc hàng đợi trong đó thực sự nối tiếp và đồng thời thực sự phát huy tác dụng.

Hãy xem xét hai hàng đợi này, một nối tiếp và một đồng thời:

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

Giả sử chúng tôi gửi hai hàng đợi đồng thời trong 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

Đầu ra của chúng bị xáo trộn (như mong đợi) nhưng lưu ý rằng mỗi hàng đợi thực hiện nhiệm vụ riêng nối tiếp. Đây là ví dụ cơ bản nhất về đồng thời - hai tác vụ chạy cùng lúc trong nền trong cùng một hàng đợi. Bây giờ hãy tạo một chuỗi đầu tiên:

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

Không phải hàng đợi đầu tiên sẽ được thực hiện nối tiếp? Đó là (và thứ hai cũng vậy). Bất cứ điều gì khác xảy ra trong nền không liên quan đến hàng đợi. Chúng tôi đã bảo hàng đợi nối tiếp thực hiện nối tiếp và nó đã làm ... nhưng chúng tôi chỉ giao cho nó một nhiệm vụ. Bây giờ hãy cho nó hai nhiệm vụ:

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

Và đây là ví dụ cơ bản nhất (và duy nhất có thể) của tuần tự hóa - hai tác vụ chạy nối tiếp (lần lượt từng bước) trong nền (đến luồng chính) trong cùng một hàng đợi. Nhưng nếu chúng ta tạo cho chúng hai hàng đợi nối tiếp riêng biệt (vì trong ví dụ trên chúng là cùng một hàng đợi), đầu ra của chúng lại bị xáo trộn:

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

Và đây là ý của tôi khi tôi nói tất cả các hàng đợi đồng thời liên quan đến nhau. Đây là hai hàng đợi nối tiếp thực hiện các nhiệm vụ của chúng cùng một lúc (vì chúng là các hàng đợi riêng biệt). Một hàng đợi không biết hoặc quan tâm đến các hàng đợi khác. Bây giờ, hãy quay lại hai hàng đợi nối tiếp (của cùng một hàng đợi) và thêm hàng đợi thứ ba, một hàng đợi đồng thời:

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

Điều đó thật bất ngờ, tại sao hàng đợi đồng thời lại đợi hàng đợi nối tiếp kết thúc trước khi nó được thực thi? Đó không phải là sự tương tranh. Sân chơi của bạn có thể hiển thị một đầu ra khác nhau nhưng tôi đã cho thấy điều này. Và nó đã cho thấy điều này bởi vì mức độ ưu tiên của hàng đợi đồng thời của tôi không đủ cao để GCD thực hiện nhiệm vụ sớm hơn. Vì vậy, nếu tôi giữ mọi thứ giống nhau nhưng thay đổi QoS của hàng đợi toàn cầu (chất lượng dịch vụ của nó, đơn giản là mức độ ưu tiên của hàng đợi) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), thì đầu ra sẽ như mong đợi:

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

Hai hàng đợi nối tiếp thực hiện các nhiệm vụ của chúng theo nối tiếp (như mong đợi) và hàng đợi đồng thời thực hiện nhiệm vụ nhanh hơn vì nó được cấp mức ưu tiên cao (QoS cao hoặc chất lượng dịch vụ).

Hai hàng đợi đồng thời, như trong ví dụ in đầu tiên của chúng tôi, hiển thị một bản in lộn xộn (như mong đợi). Để khiến chúng được in gọn gàng nối tiếp, chúng ta sẽ phải làm cho cả hai cùng một hàng đợi nối tiếp (cùng một ví dụ của hàng đợi đó, không chỉ là cùng một nhãn) . Sau đó, mỗi nhiệm vụ được thực hiện nối tiếp với nhau. Tuy nhiên, một cách khác để khiến chúng được in nối tiếp là giữ cho cả hai đồng thời nhưng thay đổi phương thức điều phối của chúng:

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

Hãy nhớ rằng, việc gửi đồng bộ chỉ có nghĩa là luồng cuộc gọi chờ cho đến khi tác vụ trong hàng đợi được hoàn thành trước khi tiếp tục. Rõ ràng, sự cảnh báo ở đây là luồng cuộc gọi bị đóng băng cho đến khi tác vụ đầu tiên hoàn thành, có thể hoặc không thể là cách bạn muốn UI thực hiện.

Và chính vì lý do này mà chúng ta không thể làm như sau:

DispatchQueue.main.sync { ... }

Đây là sự kết hợp duy nhất có thể có của các hàng đợi và các phương thức điều phối mà chúng ta không thể thực hiện việc gửi đồng bộ hóa trên hàng đợi chính. Và đó là bởi vì chúng tôi đang yêu cầu hàng đợi chính đóng băng cho đến khi chúng tôi thực hiện nhiệm vụ trong các dấu ngoặc nhọn ... mà chúng tôi đã gửi đến hàng đợi chính, thứ mà chúng tôi vừa đóng băng. Điều này được gọi là bế tắc. Để xem nó trong hành động trong một sân chơi:

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

Một điều cuối cùng cần đề cập là tài nguyên. Khi chúng tôi đưa ra một hàng đợi một nhiệm vụ, GCD tìm thấy một hàng đợi có sẵn từ nhóm được quản lý nội bộ của nó. Theo như cách viết của câu trả lời này, có 64 hàng đợi có sẵn trên mỗi qos. Điều đó có vẻ như rất nhiều nhưng chúng có thể nhanh chóng được tiêu thụ, đặc biệt là các thư viện của bên thứ ba, đặc biệt là các khung cơ sở dữ liệu. Vì lý do này, Apple có các khuyến nghị về quản lý hàng đợi (được đề cập trong các liên kết bên dưới); một là:

Thay vì tạo các hàng đợi đồng thời riêng tư, hãy gửi các tác vụ đến một trong các hàng đợi gửi đồng thời toàn cầu. Đối với các tác vụ nối tiếp, hãy đặt mục tiêu của hàng đợi nối tiếp của bạn thành một trong các hàng đợi đồng thời toàn cầu. Bằng cách đó, bạn có thể duy trì hành vi nối tiếp của hàng đợi trong khi giảm thiểu số lượng hàng đợi riêng biệt tạo chủ đề.

Để làm điều này, thay vì tạo chúng như chúng ta đã làm trước đây (mà bạn vẫn có thể), Apple khuyên bạn nên tạo hàng đợi nối tiếp như thế này:

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

Để đọc thêm, tôi khuyên bạn nên như sau:

https://developer.apple.com/lvern/archive/documentation/General/Con chấpual

https://developer.apple.com/documentation/dispatch/dispatchqueue


7

Nếu tôi hiểu đúng về cách GCD làm việc, tôi nghĩ rằng có hai loại DispatchQueue, serialconcurrent, đồng thời, có hai cách làm thế nào DispatchQueuecử nhiệm vụ của nó, giao closure, một trong những đầu tiên là async, và là khác sync. Những người cùng nhau xác định cách đóng (tác vụ) thực sự được thực thi.

Tôi thấy rằng serialconcurrentcó nghĩa là có bao nhiêu chủ đề mà hàng đợi có thể sử dụng, serialcó nghĩa là một, trong khi đó concurrentcó nghĩa là nhiều. Và syncasynccó nghĩa là nhiệm vụ sẽ được thực hiện trên đó chủ đề, chủ đề của người gọi hoặc các chủ đề cơ bản hàng đợi đó, syncphương tiện chạy trên chủ đề của người gọi trong khiasync phương tiện chạy trên các chủ đề cơ bản.

Sau đây là mã thử nghiệm có thể chạy trên sân chơi Xcode.

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()

Hy vọng nó có thể hữu ích.


7

Tôi thích nghĩ điều này bằng cách sử dụng phép ẩn dụ này (Đây là liên kết đến hình ảnh gốc):

Bố sẽ cần giúp đỡ

Hãy tưởng tượng cha bạn đang làm các món ăn và bạn vừa uống một ly soda. Bạn mang ly đến cho bố để lau chùi, đặt nó bên cạnh món ăn khác.

Bây giờ bố bạn đang tự mình làm các món ăn, vì vậy ông sẽ phải làm từng cái một: Bố của bạn ở đây đại diện cho một hàng đợi nối tiếp .

Nhưng bạn không thực sự quan tâm đến việc đứng đó và xem nó được làm sạch. Vì vậy, bạn thả kính và quay trở lại phòng của bạn: đây được gọi là công văn không đồng bộ . Cha của bạn có thể hoặc không thể cho bạn biết một khi ông đã hoàn thành nhưng điều quan trọng là bạn không đợi kính được làm sạch; bạn trở về phòng để làm, bạn biết đấy, đồ trẻ con.

Bây giờ, giả sử bạn vẫn khát và muốn có một ít nước trên cùng một chiếc cốc mà bạn thích, và bạn thực sự muốn nó trở lại ngay khi nó được làm sạch. Vì vậy, bạn đứng đó và xem cha bạn làm các món ăn cho đến khi bạn hoàn thành. Đây là một công văn đồng bộ hóa , vì bạn bị chặn trong khi bạn đang chờ nhiệm vụ kết thúc.

Và cuối cùng hãy nói rằng mẹ của bạn quyết định giúp bố của bạn và cùng ông ấy làm các món ăn. Bây giờ hàng đợi trở thành một hàng đợi đồng thời vì họ có thể làm sạch nhiều món ăn cùng một lúc; nhưng lưu ý rằng bạn vẫn có thể quyết định đợi ở đó hoặc quay trở lại phòng của mình, bất kể họ làm việc như thế nào.

Hi vọng điêu nay co ich


3

1. Tôi đang đọc rằng các hàng đợi nối tiếp được tạo và sử dụng để thực hiện lần lượt từng nhiệm vụ. Tuy nhiên, điều gì xảy ra nếu: - • Tôi tạo một hàng đợi nối tiếp • Tôi sử dụng Clark_async (trên hàng đợi nối tiếp tôi vừa tạo) ba lần để gửi ba khối A, B, C

TRẢ LỜI : - Tất cả ba khối được thực thi lần lượt. Tôi đã tạo một mã mẫu giúp hiểu.

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)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.