Back home

تعدد مؤشرات الترابط لنظام التشغيل iOS - قائمة الانتظار التسلسلية لـ GCD، وقائمة الانتظار المتزامنة، والتنفيذ المتزامن، والتنفيذ غير المتزامن

قوائم الانتظار في GCD

نظرًا للمستوى المحدود، لا يمكن ضمان صحة المحتوى التالي. يرجى قراءة المحتوى التالي بعين ناقدة. إذا وجدت أي أخطاء، يرجى تصحيحها.

1 ما هي قائمة الانتظار؟

قبل البدء بـ GCD، دعونا نتحدث عن مفهوم قائمة الانتظار، لأن جميع مهام GCD يتم إرسالها في قائمة الانتظار؛ قائمة الانتظار: عبارة عن جدول خطي للوارد أولاً يخرج أولاً (FIFO، First-In-First-Out). ومع ذلك، بعد إضافة السمتين 串行 و并发 أمام 队列، أي 串行队列 و并发队列، يكون من السهل أحيانًا الخلط، خاصة بعد إضافة مفاهيم 同步 و异步، وفي بعض الأحيان يصبح الأمر أكثر وضوحًا.

2 قائمة الانتظار التسلسلية وقائمة الانتظار المتزامنة

لاحظ أنه 并发队列(Concurrent Queue) وليس 并行队列. راجع القسم التالي لمعرفة الفرق بين 并发 و并行

ما هي 串行队列 و并发队列؟ كما هو مذكور أعلاه، 串行 و并发 في 串行队列 و并发队列 هي من سمات 队列. يمكنك إضافة ، ZXPH 00031ZX و并发的队列؛ لذا فإن 串行队列 و并行队列 لا يزالان 队列 في التحليل النهائي. نظرًا لأنه 队列، فيجب أن يكون أولًا يخرج أولاً (FIFO، أول ما يدخل أولاً يخرج)، فمن المهم تذكر ذلك.

串行队列: يعني أن المهام الموجودة في قائمة الانتظار هذه يجب تنفيذها بواسطة 串行، أي يتم تنفيذها واحدة تلو الأخرى. ويجب عليهم الانتظار حتى اكتمال المهمة السابقة قبل البدء في المهمة التالية، ويجب تنفيذها بترتيب الوارد أولاً يخرج أولاً. على سبيل المثال، هناك 4 مهام في 串行队列، وترتيب الدخول إلى قائمة الانتظار هو a، b، c، d، ثم يجب تنفيذ a أولاً، وبعد اكتمال المهمة a، b…

并发队列: يعني أنه يمكن تنفيذ المهام الموجودة في قائمة الانتظار هذه بواسطة 并发، أي أنه يمكن تنفيذ المهام في نفس الوقت. على سبيل المثال، هناك 4 مهام في 并发队列. ترتيب الدخول في قائمة الانتظار هو a، b، c، d. ثم يجب تنفيذ a أولاً، ثم b…، ولكن قد لا يكتمل a عند تنفيذ b، ومن غير المؤكد أي من a وb يتم تنفيذه أولاً. يتحكم النظام في عدد التزامن الذي يتم تنفيذه في نفس الوقت (لا يمكن تعيين عدد التزامن مباشرة في GCD، ويمكن تحقيقه عن طريق إنشاء إشارة، ويمكن تعيين NSOperationQueue مباشرة)، ولكن يجب أيضًا استدعاؤه وفقًا لمبدأ أول ما يدخل أولاً يخرج (FIFO، First-In-First-Out).

4 حول 并发 و并行

الكلمة الإنجليزية للتوازي هي التوازي، والكلمة الإنجليزية للتزامن هي التزامن.

  1. التزامن يعني التزامن في المفاهيم المنطقية، والتوازي يعني التزامن في المفاهيم الفيزيائية.

  2. يشير التزامن إلى طبيعة الكود، ويشير التوازي إلى حالة التشغيل الفعلية.

  3. التزامن يعني أن وقت بدء العملية B يقع بين وقت البدء ووقت الانتهاء من العملية A. نقول أن A وB متزامنان. يشير التوازي إلى خيطين يعملان على وحدات معالجة مركزية مختلفة في نفس الوقت.

  4. التزامن هو التعامل مع الكثير من الأشياء في وقت واحد، والتوازي هو القيام بالكثير من الأشياء في وقت واحد؛

  5. يمكن اعتبار التزامن نموذج تصميم للبنية المنطقية. يمكنك استخدام طريقة تصميم متزامنة لكتابة البرامج، ثم تشغيلها على وحدة المعالجة المركزية أحادية النواة، مما يخلق وهم التوازي من خلال التبديل المنطقي الديناميكي لوحدة المعالجة المركزية. في هذه المرحلة، برنامجك ليس متوازيًا، بل متزامنًا. إذا قمت بتشغيل برنامج متزامن على وحدة المعالجة المركزية متعددة النواة، فيمكن اعتبار برنامجك متوازيًا في هذا الوقت. التوازي يهتم أكثر بتنفيذ البرنامج (التنفيذ)؛

  6. بالنسبة لوحدة المعالجة المركزية أحادية النواة، هناك حاجة إلى وحدتي معالجة مركزية على الأقل بالتوازي؛ ولكن يمكن استخدام وحدة معالجة مركزية واحدة بالتوازي، ويمكن تنفيذ المهمتين بالتناوب؛

خلاصة القول: التزامن هو مفهوم في البرمجة، في حين أن التوازي هو مفهوم في التنفيذ الفعلي لوحدة المعالجة المركزية. يمكن تحقيق التزامن بالتوازي. يتم شرح التزامن من منظور البرمجة، ويتم عرض التوازي من منظور مهام تنفيذ وحدة المعالجة المركزية. بشكل عام، يمكننا فقط كتابة برامج متزامنة، ولكن ليس هناك ما يضمن أنه يمكننا كتابة برامج متوازية.

يمكن اعتبار التزامن والتوازي أبعادًا مختلفة. يتم النظر إلى التزامن من منظور المبرمج الذي يكتب برنامجًا. يتم رؤية التوازي من خلال التنفيذ المادي للبرنامج.

ذكر جو أرمسترونج، مخترع إرلانج، في إحدى منشوراته على مدونته (http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html) كيفية تقديم الفرق بين التزامن والتوازي لطفل عمره 5 سنوات

متزامن وغير متزامن

同步 و异步 في GCD مخصصان لتنفيذ المهام، أي التنفيذ المتزامن للمهام والتنفيذ غير المتزامن للمهام. يصف المتزامن أو غير المتزامن العلاقة بين المهمة وسياقها

التنفيذ المتزامن: يمكن أن نفهم أنه عند استدعاء وظيفة (أو عند تنفيذ كتلة التعليمات البرمجية)، يجب تنفيذ التعليمات البرمجية التالية بعد اكتمال الوظيفة (أو كتلة التعليمات البرمجية). ينفذ التنفيذ المتزامن عمومًا المهام في مؤشر الترابط الحالي ولا يبدأ موضوعًا جديدًا.

غير متزامن: بغض النظر عما إذا تم تنفيذ الوظيفة المطلوبة أم لا، سيستمر تنفيذ التعليمات البرمجية التالية. لديك القدرة على بدء مواضيع جديدة.

والفرق الرئيسي بين المتزامن وغير المتزامن هو ما إذا كان سيتم العودة فورًا عند إضافة مهمة إلى قائمة الانتظار أو الانتظار حتى تكتمل المهمة المضافة قبل العودة.

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

يُستخدم send_sync لإضافة مهام المزامنة. عند إضافة المهام، يجب عليك الانتظار حتى يتم تنفيذ التعليمات البرمجية الموجودة في الكتلة قبل أن تتمكن وظيفة Submit_sync من العودة.

يضيف Submit_async مهام غير متزامنة. عند إضافة مهمة، ستعود فورًا، بغض النظر عما إذا كان قد تم تنفيذ التعليمات البرمجية الموجودة في الكتلة أم لا.

اختبار

  1. المهام غير المتزامنة في قائمة الانتظار التسلسلية يتم تنفيذ التعليمة البرمجية التالية في طريقة 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)}

يمكنك أن ترى أن الإخراج متسلسل، في نفس الموضوع، ويتم فتح موضوع جديد.

  1. مهمة مزامنة قائمة الانتظار التسلسلية
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}

يمكنك أن ترى أن الإخراج متسلسل، في نفس الموضوع، ولكن لم يبدأ أي موضوع جديد، ويتم تنفيذه في الموضوع الرئيسي.

  1. المهام المتزامنة في قائمة الانتظار غير المتزامنة
    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)}

وكما ترون، يتم تنفيذه بشكل متزامن، ويتم فتح أكثر من موضوع جديد. هل وجدت أي شيء خاطئ هنا؟ من المفهوم أن الترتيب الذي يتم به إكمال التنفيذ غير مؤكد، ولكن لماذا يكون الترتيب الذي يبدأ به غير مؤكد أيضًا؟ وفقًا لما ورد أعلاه، فإن قائمة الانتظار تكون أولًا يخرج أولاً، لذا يجب طباعة 我开始了 بالترتيب، لكن الطباعة الفعلية معطلة. لماذا؟ ولم يتم توضيح هذه المشكلة بعد. أعتقد أن السبب قد يكون بسبب عملية NSLog(@“لقد بدأت: %@, %@”,@(i),[NSThreadcurrentThread]);.

  1. مهام مزامنة قائمة الانتظار المتزامنة
 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}

ويمكن ملاحظة أن البرنامج لا يتم تنفيذه بشكل متزامن، ولم يتم بدء أي موضوع جديد. يتم تنفيذه على الخيط الرئيسي. هل تجد ذلك غريبا؟ لماذا لا تؤدي المهام المضافة إلى قائمة الانتظار المتزامنة إلى بدء سلسلة محادثات جديدة ولكن يتم تنفيذها في سلسلة المحادثات الرئيسية؟ موضح أدناه:

使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。  
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
  
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。

上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。

هل يتم تنفيذ كافة المهام في قائمة الانتظار التسلسلية على مؤشر ترابط واحد؟

الاختبار على النحو التالي

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

يمكن ملاحظة أن مهام المزامنة المضافة إلى قائمة الانتظار التسلسلية يتم تنفيذها على مؤشر الترابط الرئيسي، وهو ما يتوافق مع الاستنتاج أعلاه (سيتم تنفيذ المهام المضافة من خلال Submit_sync في أي مؤشر ترابط تمت إضافتها). يتم تنفيذ المهمة غير المتزامنة في سلسلة رسائل مفتوحة حديثًا، ويتم فتح سلسلة رسائل واحدة فقط.

قم بإجراء الاختبار التالي مرة أخرى:

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

يمكنك أن ترى:

  • يتم تنفيذ مهام المزامنة المضافة إلى قائمة الانتظار التسلسلية المخصصة في الموضوع الرئيسي مباشرة في الموضوع الرئيسي

  • ستؤدي المهام غير المتزامنة المضافة إلى قائمة الانتظار التسلسلية المخصصة في الموضوع الرئيسي إلى فتح موضوع جديد

  • يتم تنفيذ مهام المزامنة المضافة إلى قائمة الانتظار التسلسلية المخصصة في سلاسل المحادثات غير الرئيسية مباشرة في سلسلة المحادثات الحالية

  • يتم تنفيذ المهام غير المتزامنة المضافة إلى قائمة انتظار تسلسلية مخصصة في مؤشر ترابط غير رئيسي مباشرة في مؤشر الترابط الحالي

الاستنتاج: غالبًا ما تعمل المهام المضافة إلى قائمة انتظار الإرسال التسلسلي باستخدام وظيفة Submit_sync على نفس مؤشر الترابط مثل السياق الذي تعمل فيه؛ المهام المضافة إلى قائمة انتظار الإرسال التسلسلي باستخدام وظيفة Submit_async ستفتح بشكل عام (ليس بالضرورة) موضوعًا جديدًا، لكن المهام غير المتزامنة المختلفة تستخدم نفس الموضوع.

الاختبار:

  1. هل سينفذ الخيط الرئيسي المهام في قائمة الانتظار الرئيسية فقط؟ لا، كما هو مذكور أعلاه

  2. ما هي نتيجة تنفيذ الكود التالي؟ لماذا؟

- (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}، ومن ثم يحدث حالة توقف تام. السبب: سيتم تنفيذ استخدام Submit_sync لإضافة مهام إلى قائمة الانتظار التسلسلية في مؤشر الترابط الحالي، والخيط الحالي هو الخيط الرئيسي، وبالتالي فإن أول إخراج NSLog، لأن رمز الكتلة الخاص بـ Submit_sync الأول يتم تنفيذه في مؤشر الترابط الرئيسي، لذا فإن الإرسال الثاني يعادل الكتابة التالية، لذلك سيحدث طريق مسدود. إذا كنت لا تفهم السبب، ابحث في جوجل.

- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"current thread = %@", [NSThread currentThread]);
        });
}

سيحدث الجمود،

سؤال

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

لماذا لا تبدأ بالترتيب؟ قوائم الانتظار المتزامنة هي أيضًا قوائم انتظار. يجب أن تكون قائمة الانتظار أولًا في الخروج أولاً. على الرغم من أن ترتيب نهاية التنفيذ غير مؤكد، إلا أنه يجب تحديده في البداية.

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

بعد إضافة NSLog(@“”);، يبدأ بالتسلسل. لماذا؟ إذا كنت تعرف، فلا تتردد في تعليمي.

FAQ

What to read next

Related

Continue reading