Multithreading iOS--antrean serial GCD, antrean serentak, eksekusi sinkron, dan eksekusi asinkron
Antrian di GCD
Karena levelnya yang terbatas, konten berikut tidak dijamin benar. Silakan baca konten berikut dengan pandangan kritis. Jika Anda menemukan kesalahan, harap perbaiki.
1 Apa itu antrian?
Sebelum memulai GCD, mari kita bahas konsep antrian, karena semua tugas GCD dikirim dalam antrian;
Antrian: Ini adalah tabel linear first-in, first-out (FIFO, First-In-First-Out). Namun setelah menambahkan dua atribut 串行 dan 并发 di depan 队列 yaitu 串行队列 dan 并发队列, terkadang mudah bingung, apalagi setelah menambahkan konsep 同步 dan 异步, terkadang menjadi semakin tidak jelas.
2 Antrian serial dan antrian bersamaan
Perhatikan bahwa ini adalah 并发队列(Concurrent Queue), bukan 并行队列. Lihat bagian selanjutnya untuk mengetahui perbedaan antara 并发 dan 并行
Apa itu 串行队列 dan 并发队列? Seperti disebutkan di atas, 串行 dan 并发 di 串行队列 dan 并发队列 adalah atribut dari 队列. Anda dapat menambahkan 的, ZXPH 00031ZX dan 并发的队列; jadi 串行队列 dan 并行队列 masih 队列 dalam analisa akhir. Karena 队列, maka harus masuk pertama keluar pertama (FIFO, First-In-First-Out), penting untuk mengingat hal ini.
串行队列: Artinya, tugas dalam antrian ini harus dijalankan oleh 串行, yaitu dijalankan satu per satu. Mereka harus menunggu penyelesaian tugas sebelumnya sebelum memulai tugas berikutnya, dan harus dieksekusi dengan urutan masuk pertama, keluar pertama. Misal ada 4 tugas di 串行队列, dan urutan masuk antriannya adalah a, b, c, d, maka a harus dijalankan terlebih dahulu, dan setelah tugas a selesai, b…
并发队列: Artinya tugas-tugas dalam antrian ini dapat dijalankan oleh 并发, yaitu tugas-tugas tersebut dapat dijalankan pada waktu yang sama. Misalnya, ada 4 tugas di 并发队列. Urutan masuk antrian adalah a, b, c, d. Maka a harus dieksekusi terlebih dahulu, lalu b…, tetapi a mungkin tidak selesai ketika b dieksekusi, dan tidak pasti a dan b mana yang dieksekusi terlebih dahulu. Berapa banyak yang dieksekusi pada saat yang sama dikendalikan oleh sistem (jumlah konkurensi tidak dapat diatur langsung di GCD, dapat dicapai dengan membuat semaphore, NSOperationQueue dapat diatur langsung), tetapi juga harus dipanggil sesuai dengan prinsip first-in-first-out (FIFO, First-In-First-Out).
4 Tentang 并发 dan 并行
Kata bahasa Inggris untuk paralelisme adalah paralelisme, dan kata bahasa Inggris untuk konkurensi adalah konkurensi.
-
Konkurensi berarti keserentakan dalam konsep-konsep logika, dan paralel berarti keserentakan dalam konsep-konsep fisika.
-
Konkurensi mengacu pada sifat kode, dan paralelisme mengacu pada keadaan fisik yang berjalan.
-
Konkurensi artinya waktu mulai proses B adalah antara waktu mulai dan waktu berakhirnya proses A. Kita katakan A dan B konkuren. Paralelisme mengacu pada dua thread yang berjalan pada CPU berbeda secara bersamaan.
-
Konkurensi menangani banyak hal sekaligus, dan paralelisme melakukan banyak hal sekaligus;
-
Konkurensi dapat dianggap sebagai pola desain struktur logis. Anda dapat menggunakan metode desain bersamaan untuk menulis program, dan kemudian menjalankannya pada CPU inti tunggal, menciptakan ilusi paralelisme melalui peralihan logika dinamis pada CPU. Pada titik ini, program Anda tidak paralel, tetapi bersamaan. Jika Anda menjalankan program secara bersamaan pada CPU multi-core, program Anda dapat dianggap paralel saat ini. Paralelisme lebih mementingkan pelaksanaan program (execution);
-
Untuk CPU inti tunggal, setidaknya diperlukan dua CPU secara paralel; tetapi satu CPU dapat digunakan secara paralel, dan kedua tugas tersebut dapat dijalankan secara bergantian;
Kesimpulannya: Konkurensi lebih merupakan sebuah konsep dalam pemrograman, sedangkan paralelisme adalah sebuah konsep dalam eksekusi CPU fisik. Konkurensi dapat dicapai secara paralel. Konkurensi dijelaskan dari sudut pandang pemrograman, dan paralelisme dilihat dari sudut pandang tugas eksekusi CPU. Secara umum, kita hanya dapat menulis program secara bersamaan, namun tidak ada jaminan bahwa kita dapat menulis program paralel.
Konkurensi dan paralelisme dapat dianggap sebagai dimensi yang berbeda. Konkurensi dilihat dari sudut pandang seorang programmer yang menulis suatu program. Paralelisme dilihat dari eksekusi fisik program.
Joe Armstrong, penemu Erlang, menyebutkan dalam salah satu postingan blognya (http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html) cara memperkenalkan perbedaan antara konkurensi dan paralelisme kepada anak berusia 5 tahun

Sinkron dan asinkron
同步 dan 异步 di GCD ditujukan untuk eksekusi tugas, yaitu eksekusi tugas yang sinkron dan eksekusi tugas yang tidak sinkron. Sinkron atau asinkron menggambarkan hubungan antara tugas dan konteksnya
Eksekusi sinkron: Dapat dipahami bahwa ketika suatu fungsi dipanggil (atau ketika blok kode dijalankan), kode berikut harus dieksekusi setelah fungsi (atau blok kode) selesai. Eksekusi sinkron umumnya menjalankan tugas di thread saat ini dan tidak memulai thread baru.
Asynchronous: Terlepas dari apakah fungsi yang dipanggil telah dijalankan atau belum, kode berikut akan terus dieksekusi. Memiliki kemampuan untuk memulai thread baru.
Perbedaan utama antara sinkron dan asinkron adalah apakah akan segera kembali saat menambahkan tugas ke antrean atau menunggu hingga tugas yang ditambahkan selesai sebelum kembali.
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
Dispatch_sync digunakan untuk menambahkan tugas sinkronisasi. Saat menambahkan tugas, Anda harus menunggu hingga kode di blok dijalankan sebelum fungsi pengiriman_sync dapat kembali.
pengiriman_async menambahkan tugas asinkron. Saat menambahkan tugas, tugas itu akan segera kembali, terlepas dari apakah kode di blok tersebut dijalankan.
Tes
- Tugas asinkron antrian serial Kode berikut dijalankan dalam metode viewDidLoad
dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 5; i++){
dispatch_async(serialQueue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
[NSThread sleepForTimeInterval: i % 3];
});
}
输出如下:
我开始了:0 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:1 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:2 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:3 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:4 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
Anda dapat melihat bahwa outputnya berurutan, di thread yang sama, dan thread baru dibuka.
- Tugas sinkronisasi antrian serial
for(int i = 0; i < 5; i++){
dispatch_sync(serialQueue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
[NSThread sleepForTimeInterval: i % 3];
});
}
输出如下:
我开始了:0 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:1 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:2 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:3 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:4 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
Anda dapat melihat bahwa outputnya berurutan, di thread yang sama, tetapi tidak ada thread baru yang dimulai, dan dijalankan di thread utama.
- Tugas asinkron antrian bersamaan
dispatch_queue_t concurrent_queue = dispatch_queue_create("DanCONCURRENT", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_async(concurrent_queue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
[NSThread sleepForTimeInterval: i % 3];
NSLog(@"执行完成:%@ , %@",@(i),[NSThread currentThread]);
});
}
输出如下:
我开始了:0 , <NSThread: 0x600000462340>{number = 3, name = (null)}
我开始了:2 , <NSThread: 0x604000269380>{number = 6, name = (null)}
我开始了:3 , <NSThread: 0x604000269180>{number = 5, name = (null)}
我开始了:1 , <NSThread: 0x600000461d80>{number = 4, name = (null)}
执行完成:0 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:3 , <NSThread: 0x604000269180>{number = 5, name = (null)}
我开始了:4 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:1 , <NSThread: 0x600000461d80>{number = 4, name = (null)}
执行完成:4 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:2 , <NSThread: 0x604000269380>{number = 6, name = (null)}
Seperti yang Anda lihat, ini dijalankan secara bersamaan, dan lebih dari satu thread baru dibuka.
Apakah Anda menemukan sesuatu yang salah di sini? Dapat dimengerti bahwa urutan penyelesaian eksekusi tidak pasti, namun mengapa urutan dimulainya juga tidak pasti? Berdasarkan penjelasan di atas, antriannya adalah first in first out, jadi 我开始了 harusnya dicetak secara berurutan, tetapi pencetakan sebenarnya rusak. Mengapa? Masalah ini belum diklarifikasi. Dugaan saya adalah hal ini mungkin disebabkan oleh pengoperasian NSLog yang memakan waktu(@“Saya memulai: %@, %@”,@(i),[NSThread currentThread]);.
- Tugas sinkronisasi antrian secara bersamaan
for(int i = 0; i < 5; i++){
dispatch_sync(concurrent_queue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
[NSThread sleepForTimeInterval: i % 3];
NSLog(@"执行完成:%@ , %@",@(i),[NSThread currentThread]);
});
}
输出如下:
我开始了:0 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:0 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:1 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:1 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:2 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:2 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:3 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:3 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:4 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:4 , <NSThread: 0x60000007ec80>{number = 1, name = main}
Terlihat bahwa program tidak dijalankan secara bersamaan, dan tidak ada thread baru yang dimulai. Itu dijalankan di thread utama. Apakah menurut Anda itu aneh? Mengapa tugas yang ditambahkan ke antrian bersamaan tidak memulai thread baru tetapi dijalankan di thread utama? Dijelaskan di bawah ini:
使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。
上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。
Apakah semua tugas dalam antrian serial dijalankan pada satu thread?
Tesnya adalah sebagai berikut
- (void)viewDidLoad {
dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
// block 1
NSLog(@"current 1: %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
// block 2
NSLog(@"current 2: %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
// block 3
NSLog(@"current 3: %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
// block 4
NSLog(@"current 4: %@", [NSThread currentThread]);
});
}
//结果如下
// current 1: <NSThread: 0x600000071600>{number = 1, name = main}
// current 2: <NSThread: 0x600000071600>{number = 1, name = main}
// current 3: <NSThread: 0x60400027bcc0>{number = 3, name = (null)}
// current 4: <NSThread: 0x60400027bcc0>{number = 3, name = (null)}
Dapat dilihat bahwa tugas sinkronisasi yang ditambahkan ke antrian serial dijalankan di thread utama, yang konsisten dengan kesimpulan di atas (tugas yang ditambahkan melalui pengiriman_sync akan dieksekusi di thread mana tugas tersebut ditambahkan). Tugas asinkron dijalankan di thread yang baru dibuka, dan hanya satu thread yang dibuka.
Lakukan tes berikut lagi:
- (void)viewDidLoad {
dispatch_queue_t queue = dispatch_queue_create("Dan", NULL);
dispatch_async(queue, ^{
NSLog(@"current : %@", [NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
// block 1
NSLog(@"current 1: %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
// block 2
NSLog(@"current 2: %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
// block 3
NSLog(@"current 3: %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
// block 4
NSLog(@"current 4: %@", [NSThread currentThread]);
});
});
}
// 结果如下
// current : <NSThread: 0x604000263440>{number = 3, name = (null)}
// current 1: <NSThread: 0x604000263440>{number = 3, name = (null)}
// current 2: <NSThread: 0x604000263440>{number = 3, name = (null)}
// current 3: <NSThread: 0x604000263440>{number = 3, name = (null)}
// current 4: <NSThread: 0x604000263440>{number = 3, name = (null)}
Anda dapat melihat:
-
Tugas sinkronisasi yang ditambahkan ke antrian serial khusus di thread utama dijalankan langsung di thread utama
-
Tugas asinkron yang ditambahkan ke antrian serial khusus di thread utama akan membuka thread baru
-
Tugas sinkronisasi yang ditambahkan ke antrian serial khusus di thread non-utama dijalankan langsung di thread saat ini
-
Tugas asinkron yang ditambahkan ke antrian serial khusus di thread non-utama dijalankan langsung di thread saat ini
Kesimpulan: Tugas yang ditambahkan ke antrian pengiriman serial menggunakan fungsi pengiriman_sync sering kali dijalankan di thread yang sama dengan konteks di mana tugas tersebut dijalankan; tugas yang ditambahkan ke antrian pengiriman serial menggunakan fungsi pengiriman_async umumnya (belum tentu) akan membuka thread baru, tetapi tugas asinkron yang berbeda menggunakan thread yang sama.
Tes:
-
Apakah thread utama hanya akan menjalankan tugas di antrian utama? Tidak, seperti di atas
-
Apa hasil dari mengeksekusi kode berikut? Mengapa?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("com.dan.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"current thread = %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current thread = %@", [NSThread currentThread]);
});
});
}
current thread = <NSThread: 0x60000006d600>{number = 1, name = main} dikeluarkan, dan kemudian terjadi kebuntuan.
Alasan: Menggunakan Dispatch_sync untuk menambahkan tugas ke antrian serial akan dieksekusi di thread saat ini, dan thread saat ini adalah thread utama, jadi output NSLog pertama, karena kode blok Dispatch_sync pertama dieksekusi di thread utama, sehingga Dispatch_sync kedua setara dengan penulisan berikut, sehingga akan terjadi kebuntuan. Jika Anda tidak mengerti alasannya, cari di Google.
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current thread = %@", [NSThread currentThread]);
});
}
Kebuntuan akan terjadi,
Pertanyaan
- (void)viewDidLoad {
dispatch_queue_t concurrent_queue = dispatch_queue_create("Dan——CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 10; i++){
dispatch_async(concurrent_queue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
});
}
}
//运行结果如下
//我开始了:3 , <NSThread: 0x60000026e240>{number = 6, name = (null)}
//我开始了:1 , <NSThread: 0x60400027a440>{number = 4, name = (null)}
//我开始了:2 , <NSThread: 0x60000026f800>{number = 5, name = (null)}
//我开始了:0 , <NSThread: 0x60400027a400>{number = 3, name = (null)}
//我开始了:4 , <NSThread: 0x60000026e240>{number = 6, name = (null)}
//我开始了:5 , <NSThread: 0x60400027a440>{number = 4, name = (null)}
//我开始了:8 , <NSThread: 0x60000026e980>{number = 9, name = (null)}
//我开始了:7 , <NSThread: 0x60000026e800>{number = 8, name = (null)}
//我开始了:6 , <NSThread: 0x60000026e8c0>{number = 7, name = (null)}
//我开始了:9 , <NSThread: 0x60400027a280>{number = 10, name = (null)}
Mengapa tidak memulai secara berurutan? Antrian serentak juga merupakan antrian. Antriannya harus masuk pertama, keluar pertama. Meskipun urutan akhir pelaksanaannya tidak pasti, namun harus ditentukan sejak awal.
- (void)viewDidLoad {
dispatch_queue_t concurrent_queue = dispatch_queue_create("Dan——CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 10; i++){
dispatch_async(concurrent_queue, ^{
NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
});
}
NSLog(@"");
}
//运行结果如下
//我开始了:0 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:1 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:2 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:3 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:4 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:5 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:6 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:7 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:8 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:9 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
Setelah menambahkan NSLog(@“”);, itu dimulai secara berurutan. Mengapa? Jika Anda tahu, silakan ajari saya.
What to read next
Want more posts about iOS?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #iOS?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home