首页 > 编程语言 >iOS开发-多线程编程

iOS开发-多线程编程

时间:2024-07-22 16:41:21浏览次数:10  
标签:执行 队列 编程 iOS dispatch queue 任务 线程 多线程

OC中常用的多线程编程技术:

1. NSThread

NSThread是Objective-C中最基本的线程抽象,它允许程序员直接管理线程的生命周期。

NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];
[myThread start];

使用NSThread时,需要自己管理线程的生命周期,包括创建、启动和销毁线程。这种方法给了开发者很大的控制权,但也增加了复杂性,因为需要手动处理线程同步和线程安全问题。

2. Grand Central Dispatch (GCD)

GCD是一个强大的基于C语言的API,它提供了一个并发执行任务的低级别方式。GCD使用任务队列和线程池来优化线程的使用。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 异步执行的任务
});

GCD是推荐的多线程编程方法之一,因为它的性能很好,而且简化了并发编程的复杂性(下一节详细介绍)。

3. Operation Queues

NSOperationNSOperationQueue提供了一个面向对象的方式来执行并发操作。NSOperation是一个抽象类,可以通过继承它来定义具体的操作。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod) object:nil];
[queue addOperation:operation];

NSOperationQueue可以管理多个NSOperation对象,它支持设置最大并发操作数和依赖关系。NSOperation比GCD更高级,它支持取消操作、设置操作依赖和观察操作状态。

4. Perform Selector Methods

Objective-C提供了performSelector:onThread:等方法来在指定的线程上执行方法。

[self performSelector:@selector(myTaskMethod) onThread:myThread withObject:nil waitUntilDone:NO];

这些方法简单易用,但它们不提供GCD和NSOperation的强大功能。

5. POSIX Threads (pthreads)

POSIX线程是一套跨平台的线程相关的API,Objective-C可以直接使用这些API,因为它是C语言的超集。

#include <pthread.h>

void *myThreadFunction(void *context) {
    // 线程执行的代码
    return NULL;
}

pthread_t thread;
pthread_create(&thread, NULL, myThreadFunction, NULL);

pthreads提供了很大的控制力,但它是一个低级别的API,通常不推荐在OC中使用,除非需要与C库交互或有特殊的线程管理需求。

GCD的详细介绍

Grand Central Dispatch (GCD) 是 Apple 开发的一个强大的多核编程解决方案,它提供了一种简单且高效的方式来管理并发任务。GCD 使用任务(blocks of code)和队列(queues)的概念来执行工作。它是基于 C 语言实现的,可以在 Objective-C 和 Swift 中使用。

核心概念

任务

任务是指要执行的工作,通常是以 block 的形式提供。在 Objective-C 中,一个任务可以是一个 block 或者一个函数。

队列

队列是一种特殊的数据结构,用于按顺序存储任务。GCD 提供了两种类型的队列:

  • 串行队列(Serial Queue):一次只执行一个任务。队列中的任务按照添加的顺序依次执行。在内部实现了一种机制,确保队列中的任务一次只能执行一个,并且按照它们被添加到队列中的顺序来执行。这是通过队列管理和任务调度来实现的。

当将一个任务提交到串行队列时,这里是大致的工作流程:

  1. 任务排队:任务被添加到队列的末尾。如果队列是空的,它会成为队列中的第一个任务。

  2. 任务执行:队列中的第一个任务(队头)被取出来执行。在这个任务执行期间,队列不会执行或者开始执行任何其他任务。

  3. 任务完成:一旦当前执行的任务完成,它会从队列中移除。

  4. 下一个任务:队列中的下一个任务(现在是队头)开始执行。

  5. 重复过程:这个过程会一直重复,直到队列中的所有任务都被执行完毕。

串行队列的关键特性是互斥执行,这意味着在任何给定时间点,队列中只有一个任务在执行。这种互斥是由GCD的底层调度机制保证的,它确保了即使在多核处理器上,串行队列上的任务也不会并行执行。

这种执行方式使得串行队列成为了同步执行任务的理想选择,特别是需要按顺序执行一系列任务,而这些任务又不能同时执行时(例如,当任务需要按特定顺序访问或修改共享资源时)。因此理论上一个线程就足够了。这是串行队列如何保证任务顺序执行和互斥的关键。当一个任务在串行队列中开始执行时,它会持续运行直到完成,然后队列才会执行下一个任务。这个过程不需要同时有多个线程参与,因为不会有并行执行的情况。然而,实际上,由于GCD的工作原理,它可能会在内部使用多个线程来管理多个串行队列。GCD使用线程池来优化线程的使用,这意味着它会根据需要动态地为队列分配和回收线程。但对于任何单一的串行队列来说,你可以认为它在任何时候都只在一个线程上执行任务。这种设计使得串行队列成为管理共享资源和避免并发问题的理想工具,因为它简化了同步和线程安全的需求。同时,它也减少了上下文切换的开销,因为任务是在单个线程上连续执行的。

  • 并行队列(Concurrent Queue):可以同时执行多个任务。任务可以并发执行,但完成的顺序可能会不同。

系统队列

GCD 提供了几种不同类型的系统队列:

  • 主队列(Main Queue):串行队列,用于在主线程上执行任务,通常用于更新 UI。
  • 全局队列(Global Queues):并行队列,有四个不同优先级的全局队列:高、默认、低和后台。

Grand Central Dispatch (GCD) 提供了几种不同类型的系统队列,这些队列是预先创建好的,可以直接使用。它们分为两大类:主队列(Main Queue)和全局队列(Global Queues)。

主队列(Main Queue)
  • 主队列是一个特殊的串行队列,它在应用程序的主线程上执行任务。因为主线程通常用于更新UI,所以所有的UI更新都应该在主队列上执行,以确保UI的平滑和响应性。
  • 使用dispatch_get_main_queue()函数可以获取主队列。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
    // 在主线程上更新UI
});
全局队列(Global Queues)
  • 全局队列是并行队列,它们在后台执行任务,不会阻塞主线程。全局队列有四个不同的优先级:高(high)、默认(default)、低(low)和后台(background)。这些优先级对应于系统为任务分配的相对重要性。
  • 使用dispatch_get_global_queue()函数可以获取全局队列,需要指定优先级和一个保留用的标志位(目前应该传递0)。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 在后台执行耗时任务
});

全局队列的优先级:

  • 高优先级DISPATCH_QUEUE_PRIORITY_HIGH):用于需要立即执行的任务,但不应该阻塞主线程。
  • 默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT):用于大多数任务,如果没有特殊的优先级要求,应该使用这个优先级。
  • 低优先级DISPATCH_QUEUE_PRIORITY_LOW):用于不急迫的任务,可以等待其他更重要的任务完成后再执行。
  • 后台优先级DISPATCH_QUEUE_PRIORITY_BACKGROUND):用于那些用户不太可能立即注意到的任务,如预取数据、维护或清理工作。
    注意以下事项:
  • 尽管全局队列是并行队列,但是任务的启动顺序仍然是按照它们被添加到队列的顺序。
  • 全局队列不保证任务完成的顺序,任务可以并发执行。
  • 主队列保证任务按照添加的顺序一个接一个地执行。
  • 在主队列上同步执行任务会导致死锁,因为主队列等待同步任务完成,而同步任务又在等待主队列可用,从而形成了相互等待的情况。

自定义队列

除了系统队列,GCD还允许创建自定义队列。自定义队列可以是串行的也可以是并行的。

  • 创建串行队列:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.mySerialQueue", DISPATCH_QUEUE_SERIAL);
  • 创建并行队列:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

使用 GCD

异步执行

使用 dispatch_async 函数可以异步地将任务提交到队列中。这意味着它不会等待任务完成,而是立即返回。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 执行耗时的任务
});

同步执行

使用 dispatch_sync 函数可以同步地将任务提交到队列中。这会阻塞当前线程,直到任务执行完成。

dispatch_sync(queue, ^{
    // 执行任务
});

延迟执行

使用 dispatch_after 函数可以在指定的时间后异步执行任务。

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
    // 2秒后执行的任务
});

一次性执行

使用 dispatch_once 函数可以确保代码块只被执行一次,常用于创建单例。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行一次的代码
});

队列组

dispatch_group 允许多个任务作为一个组来提交,并在组中的所有任务完成时得到通知。

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    // 任务1
});
dispatch_group_async(group, queue, ^{
    // 任务2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 所有任务完成后执行
});

信号量

dispatch_semaphore 用于控制访问资源的线程数量,可以用来实现线程同步。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    // 访问受限资源
    dispatch_semaphore_signal(semaphore);
});

注意事项

  • 不要在串行队列上同步地执行任务,这可能会导致死锁。
  • 尽量避免在主队列上同步执行耗时任务,这会阻塞 UI 更新。
  • 使用 GCD 时,要注意内存管理,特别是在 block 中捕获外部变量时。

标签:执行,队列,编程,iOS,dispatch,queue,任务,线程,多线程
From: https://www.cnblogs.com/jianqiu/p/18316118

相关文章

  • 模块2 面向对象编程初级 --- 第四章:创建类
    第四章 创建类主要知识点:1、类的定义2、类的修饰学习目标:掌握类的定义方法,能够编写简单的类。4.1类的定义问题空间元素在方法空间中的表示称为对象,面向对象的程序设计是以解决的问题中所涉及到的各种对象为主要考虑因素,更加贴近于人的思维方式,面向对象程......
  • iOS面试题-load 和 initlize的区别
    +load和+initialize是两个与类的加载和初始化相关的特殊方法。它们在类的生命周期中的作用和调用时机有明显的区别。+load方法调用时机:+load在类初始加载进内存时调用,这通常发生在程序启动的时候,所有类和分类(Category)的+load方法在应用程序的生命周期中只会被调用一次。调用......
  • Android或iOS 与 REST/SOAP测试 工具推荐
    移动测试工具- 有助于自动测试Android或iOS应用程序1)AppiumAppium是用于移动应用程序自动化的开源测试工具之一。它允许用户测试各种原生、移动、web和混合应用程序。它还支持模拟器和模拟器上的自动测试。功能特点:这是一个简单的应用程序,需要很少的内存用于测试过程......
  • Java语言程序设计基础篇_编程练习题**15.17 (几何问题:寻找边界矩形)
    **15.17(几何问題:寻找边界矩形)请编写一个程序,让用户可以在一个二维面板上动态地增加和移除点,如图15-29a所示。当点加入和移除的时候,一个最小的边界矩形更新显示。假设每个点的半径是10像素解题思路:这道题可以从编程练习题15.15修改新建一个面板Pane(),方法外部新建一个......
  • iOS开发基础135-Core Data
    Objective-C(OC)中使用CoreData是iOS应用开发中管理模型层对象的一种有效工具。CoreData使用ORM(对象关系映射)技术来抽象化和管理数据。这不仅可以节省时间,还能减少编程错误。以下是使用CoreData的详细介绍,包括示例代码,以及深入底层的一些分析。基本概念持久化......
  • bluez glib编程 --- signal 信号的监听
    监听添加新对象的信号objectManager=g_dbus_proxy_new_sync(conn,G_DBUS_PROXY_FLAGS_NONE,NULL,"org.bluez","/","org.freede......
  • 单位网络监控软件中的Pharo面向对象编程
    Pharo是一种现代化的面向对象编程语言,基于Smalltalk语言的理念。在单位网络监控软件的开发中,Pharo提供了强大的面向对象功能,可以帮助开发者更好地组织和管理代码。在本文中,我们将探讨Pharo语言在网络监控软件中的应用,并提供一些代码示例。Pharo的基本概念Pharo是一种动态......
  • C# 网络编程:.NET 开发者的核心技能
    前言数字化时代,网络编程已成为软件开发中不可或缺的一环,尤其对于.NET开发者而言,掌握C#中的网络编程技巧是迈向更高层次的必经之路。无论是构建高性能的Web应用,还是实现复杂的分布式系统,网络编程都是支撑这一切的基石。本篇主要为.NET开发者提供一份全面而精炼的C#网络......
  • 博文标题:探索Python中的元编程:装饰器的魔法
    引言在Python的世界里,装饰器(Decorators)是一种非常强大的特性,它允许程序员在不修改原始函数代码的情况下,为函数添加新的功能。这种机制不仅增强了代码的可读性和可维护性,还提供了高度的灵活性和扩展性。本文将深入探讨装饰器的基本概念、工作原理以及如何利用它们来简化和......
  • 封禁 NetBIOS Session Service 和 SMB 服务(特别是旧版本的SMB)可能是出于安全性考虑。
    封禁NetBIOSSessionService和SMB服务(特别是旧版本的SMB)可能是出于安全性考虑。这两种服务在过去的实现中存在一些安全漏洞和风险,特别是在现代网络环境中,这些风险可能会被利用来进行攻击或者未经授权的访问。下面是一些常见的安全考虑:中间人攻击:未加密的NetBIOS和旧版本......