Skip to content

多线程

面试点

GCD, NSOperation, NSThread, 多线程与锁

GCD

1. 同步/异步 和 串行/并行

1. 同步串行

dispatch_sync(serial_queue, ^{});

- (void)viewDid Load {
    dispatch_sync(dispatch_get_main_queue), ^
        [self doSomething];
    });
}

队列引起的循环等待? 我有疑问, 先理清一些概念。

理解主队列和主线程的关系

  • 主队列中的任务是先进先出(FIFO)顺序。
  • 主线程负责执行主队列中的任务。
  • 在主线程中执行任务时,如果遇到同步提交到主队列的任务,会等待该任务完成。

解释

  1. viewDidLoad在主线程上执行:当viewDidLoad方法被调用时,它在主线程上执行。

  2. dispatch_sync(dispatch_get_main_queue(), ^{ ... })被调用:在viewDidLoad方法中,dispatch_sync将一个任务同步提交到主队列,并且主线程会等待这个任务完成。

  3. 任务进入主队列:这个dispatch_sync提交的任务(block)被添加到主队列中,但是主队列当前正在执行viewDidLoad

  4. 主线程等待:dispatch_sync阻塞了主线程,主线程在等待主队列中的这个block完成。

  5. 主队列中的任务执行顺序:

    • viewDidLoad 方法是当前正在执行的任务。
    • dispatch_sync提交的block是下一个等待执行的任务。
  6. 死锁形成:

    • 主队列中的下一个任务(block)需要主线程来执行。
    • 但是主线程被dispatch_sync阻塞,等待这个block完成。
    • 结果,主线程在等待主队列中的任务完成,而主队列中的任务需要主线程来执行,形成循环等待,导致死锁。
- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_sync(serialQueue, ^{
        [self doSomething];
    });
}

  1. 串行队列和主线程的关系:

    • 如果serialQueue是主队列(dispatch_get_main_queue()),这将导致死锁,因为主线程会等待自己完成任务。
    • 如果serialQueue是一个与主队列无关的串行队列(例如通过dispatch_queue_create创建的队列),则不会导致死锁,但可能会影响性能,因为它阻塞了主线程,直到任务完成。
  2. 执行情况:

    • 主线程在执行viewDidLoad时调用dispatch_sync
    • dispatch_sync将任务同步提交到serialQueue,并阻塞当前线程(主线程)直到任务完成。
    • 如果serialQueue不是主队列,任务会在serialQueue上执行,完成后主线程继续执行。
  3. 可能出现的问题

    • 性能问题:即使没有死锁,使用dispatch_sync会阻塞主线程,直到任务完成。这会阻止主线程执行其他任务,可能导致界面卡顿或延迟响应用户操作。
    • 死锁风险:如果不小心把serialQueue设成主队列(例如通过错误配置),则会导致死锁。
  4. 总结

    • 使用dispatch_sync将任务同步提交到一个与主队列无关的串行队列不会导致死锁,但会阻塞主线程,影响性能。
    • 使用dispatch_sync将任务提交到主队列会导致死锁。
    • 最好使用dispatch_async,这样不会阻塞主线程,能保证界面的流畅性和响应性。
  5. 这种情况通常发生在下面两种情况下:

    • 在主线程中使用同步任务提交到主队列:主线程在等待任务完成,而主队列中的任务需要主线程来执行,形成循环等待,导致死锁。

    • 在自定义串行队列中使用同步任务:如果在自定义的串行队列中使用了同步任务,而这些任务之间又相互等待完成,同样会导致死锁。

因此,在编写多线程代码时,应该特别注意避免在同一个线程中使用同步任务提交到同一个串行队列,以防止死锁的发生。通常情况下,使用异步任务提交(dispatch_async)能够避免这种死锁。

2. 同步并发

dispatch_sync(concurrent_queue, ^{});

- (void)viewDidLoad {

    NSLog(@" 1" );
    NSLog(@"1 -%@",[NSThread currentThread]);

    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2");
        NSLog(@"2 -%@",[NSThread currentThread]);

        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3");
            NSLog(@"3 -%@",[NSThread currentThread]);
        });

        NSLog(@"4");
        NSLog(@"4 -%@",[NSThread currentThread]);
    });

    NSLog(@" 5" );
    NSLog(@"5 -%@",[NSThread currentThread]);

}

// 1,2,3,4,5  都在 main 线程执行

只要以同步方式提交任务,无论是提交到串行队列还是并发队列,都是在当前的线程中执行。

3. 异步串行

dispatch_async(serial_queue, ^{});

- (void)viewDidLoad {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
    });
}

4. 异步并发

dispatch_async(concurrent_queue, ^{});

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(printLog) withObject:nil afterDelay:1];
        NSLog(@"3");
    });
}

- (void)printLog {
    NSLog(@"2");
}

// 1,3
  1. dispatch_async会在全局队列上异步执行其块。
  2. 在块内,首先NSLog(@"1");会执行,输出"1"。
  3. 接着,[self performSelector:@selector(printLog) withObject:nil afterDelay:1];这一行并不会立即调用printLog方法。相反,它会在当前线程(注意:此时是全局队列的一个线程)的runloop上设置一个定时器,1秒后执行printLog方法。但是,由于这个块是在一个后台队列上运行的,并且该队列没有与之关联的runloop,所以这个定时器实际上不会被触发。
  4. 紧接着,NSLog(@"3");会执行,输出"3"。

2. dispatch_barrier_async

dispatch_barrier_async 是 GCD(Grand Central Dispatch)中的一个有用函数,主要用于并发队列的读写操作。当你需要在并发队列中执行写操作时,可以使用 dispatch_barrier_async 来确保写操作的独占性,防止数据竞争问题。

NS_ASSUME_NONNULL_BEGIN

@interface ThreadSafeArray : NSObject
- (void)addObject:(id)object;
- (id)objectAtIndex:(NSUInteger)index;
@end

NS_ASSUME_NONNULL_END

#import "ThreadSafeArray.h"

@implementation ThreadSafeArray {
    NSMutableArray *_array;
    dispatch_queue_t _queue;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _array = [NSMutableArray array];
        _queue = dispatch_queue_create("com.example.ThreadSafeArrayQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)addObject:(id)object {
    dispatch_barrier_async(_queue, ^{
        [self ->_array addObject:object];
    });
}

- (id)objectAtIndex:(NSUInteger)index {
    __block id result = nil;
    dispatch_sync(_queue, ^{
        if (index < [_array count]) {
            result = [_array objectAtIndex:index];
        }
    });
    return result;
}


@end

---
ThreadSafeArray *safeArray = [[ThreadSafeArray alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
    for (int i = 0; i < 100; i++) {
        [safeArray addObject:@(i)];
    }
});

dispatch_async(queue, ^{
    for (int i = 0; i < 100; i++) {
        NSLog(@"%@ - %@", [safeArray objectAtIndex:i], [NSThread currentThread]);
    }
});


解释:

  1. 创建并发队列:在 ThreadSafeArray 类中,创建一个并发队列 _queue,用于管理对 _array 的并发访问。
  2. 写操作(添加对象):使用 dispatch_barrier_async 来确保写操作的独占性。即当一个写操作正在进行时,其他的读操作和写操作都会被阻塞,直到该写操作完成。
  3. 读操作(读取对象):使用 dispatch_sync 来确保读操作的线程安全性。在并发队列中执行读操作时,不会阻塞其他的读操作,但会被正在进行的写操作阻塞。

使用场景:

  1. 线程安全的数据结构:比如上面的例子,使用 dispatch_barrier_async 实现线程安全的数组、字典等数据结构。

  2. 数据库访问:在多个线程中并发访问数据库时,可以使用 dispatch_barrier_async 来确保写操作的独占性,防止数据竞争和数据不一致的问题。

  3. 文件读写:在多个线程中并发读写文件时,可以使用 dispatch_barrier_async 来确保写操作的独占性,防止文件数据的损坏。

3. dispatch_group

dispatch_group 是 GCD 中的一个功能,用于监控一组任务的执行,并在所有任务完成时执行某个操作。它可以让你在一组并发任务完成后执行一些后续操作,而无需手动管理每个任务的完成状态。

示例:

假设你需要从多个 URL 下载图片,并在所有图片下载完成后,将它们合成为一张图片并显示。这时就可以使用 dispatch_group 来管理这些下载任务。

#import <UIKit/UIKit.h>

@interface ImageDownloader : NSObject

- (void)downloadImagesAndMerge;

@end

@implementation ImageDownloader

- (void)downloadImagesAndMerge {
    NSArray *imageURLs = @[
        [NSURL URLWithString:@"https://example.com/image1.jpg"],
        [NSURL URLWithString:@"https://example.com/image2.jpg"],
        [NSURL URLWithString:@"https://example.com/image3.jpg"]
    ];

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageURLs.count];

    for (NSURL *url in imageURLs) {
        dispatch_group_enter(group);
        [self downloadImageFromURL:url completion:^(UIImage *image) {
            if (image) {
                @synchronized (images) {
                    [images addObject:image];
                }
            }
            dispatch_group_leave(group);
        }];
    }

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        UIImage *mergedImage = [self mergeImages:images];
        [self displayImage:mergedImage];
    });
}

- (void)downloadImageFromURL:(NSURL *)url completion:(void (^)(UIImage *))completion {
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        UIImage *image = nil;
        if (data) {
            image = [UIImage imageWithData:data];
        }
        if (completion) {
            completion(image);
        }
    }];
    [task resume];
}

- (UIImage *)mergeImages:(NSArray<UIImage *> *)images {
    CGSize size = CGSizeMake(100, 100); // 假设每张图片的尺寸
    UIGraphicsBeginImageContext(size);
    for (UIImage *image in images) {
        [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    }
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result;
}

- (void)displayImage:(UIImage *)image {
    // 假设这是一个控制器的方法
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [self.view addSubview:imageView];
}

@end

解释:

  1. 创建 dispatch_group:dispatch_group_t group = dispatch_group_create();

    • 创建一个新的调度组,用于跟踪多个异步任务的完成情况。
  2. 提交任务到组中:使用 dispatch_group_enter(group) 和 dispatch_group_leave(group)。

    • dispatch_group_enter(group):表示将一个任务添加到组中。
    • dispatch_group_leave(group):表示一个任务已经完成。
  3. 异步下载图片:通过 NSURLSession 下载图片。

    • 每个图片下载任务都使用 dispatch_group_enter 标记进入组,在完成时使用 dispatch_group_leave 标记离开组。
  4. 等待组中所有任务完成:使用 dispatch_group_notify。

    • dispatch_group_notify(group, dispatch_get_main_queue(), ^{...}):当组中所有任务都完成后,在主队列中执行合并图片并显示图片的操作。

应用场景

  1. 批量网络请求:比如你需要同时向多个服务器发送请求,所有请求完成后再进行统一处理。
  2. 并行任务的同步点:在多任务并行处理后,需要在所有任务完成时执行某个后续操作。
  3. 并行数据处理:比如你需要并行处理多个数据块,所有数据处理完成后进行汇总计算。
  4. 任务依赖:在多个并发任务完成后启动依赖于这些任务结果的后续任务。

使用 dispatch_group 可以简化并发任务的管理,确保后续操作在所有并发任务完成后进行,有效避免了回调地狱和复杂的状态管理。

NSOperation

NSOperationQueue 和 NSOperation 提供了一种高级的、面向对象的方式来管理和执行并发任务。NSOperation 是一个抽象类,通常通过其子类来定义具体的操作(NSOperation的两个子类NSBlockOperation,NSInvocationOperation)。

  • 添加任务依赖
  • 任务执行状态控制
  • 最大并发数

任务执行状态控制

  • isReady
  • isExecuting
  • isFinished
  • isCancelled
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 3;

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // 执行第一个任务
    NSLog(@"Task 1");
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // 执行第二个任务
    NSLog(@"Task 2");
}];

NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    // 执行第三个任务
    NSLog(@"Task 3");
}];

NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 所有任务完成后执行的统一处理
    NSLog(@"All tasks are completed");
}];

// 设置依赖关系
[completionOperation addDependency:operation1];
[completionOperation addDependency:operation2];
[completionOperation addDependency:operation3];

[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
[operationQueue addOperation:operation3];
[operationQueue addOperation:completionOperation];

// 创建一个 NSInvocationOperation,并指定执行的方法和对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(someMethod:) object:nil];

// 创建一个队列,用于执行操作
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

// 将操作加入队列,会在后台线程中执行指定的方法
[operationQueue addOperation:operation];

自定义 NSOperation

@interface CustomOperation : NSOperation
@property (nonatomic, strong) NSString *taskName;
@end

@implementation CustomOperation

- (instancetype)initWithTaskName:(NSString *)taskName {
    self = [super init];
    if (self) {
        _taskName = taskName;
    }
    return self;
}

// 重写 main 方法
- (void)main {
    @autoreleasepool {
        if (self.isCancelled) {
            return;
        }
        NSLog(@"Executing Task: %@", self.taskName);

        // 模拟任务执行
        [NSThread sleepForTimeInterval:2];

        if (self.isCancelled) {
            return;
        }

        NSLog(@"Completed Task: %@", self.taskName);
    }
}

// 或者重写 start 方法
- (void)start {
    if (self.isCancelled) {
        return;
    }

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    // 设置操作的状态
    _executing = YES;
    _finished = NO;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];

    // 在主线程中执行
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        if (self.isCancelled) {
            [self completeOperation];
            return;
        }

        NSLog(@"Executing Task: %@", self.taskName);

        // 模拟任务执行
        [NSThread sleepForTimeInterval:2];

        if (self.isCancelled) {
            [self completeOperation];
            return;
        }

        NSLog(@"Completed Task: %@", self.taskName);
        [self completeOperation];
    }];
}

// 完成操作时设置状态
- (void)completeOperation {
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    _executing = NO;
    _finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return _executing;
}

- (BOOL)isFinished {
    return _finished;
}

@end

如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出。

如果重写了start方法,自行控制任务状态

面试题:

系统是怎样移除一个isFinished=YES的NSOperation的?

答案:通过KVO

NSThread

NSThread 是基于底层的 POSIX 线程实现的,而 POSIX 线程是操作系统级别的线程。NSThread 封装了底层线程的创建和管理,提供了面向对象的接口供开发者使用。

// 创建一个对象,用于在新线程中执行任务
@interface MyTaskObject : NSObject
- (void)performTask;
@end

@implementation MyTaskObject

- (void)performTask {
    @autoreleasepool {
        // 在这里编写你想在新线程中执行的任务
        NSLog(@"Task is executing in thread: %@", [NSThread currentThread]);
    }
}

@end

// 在主线程中创建并启动一个新线程
NSThread *newThread = [[NSThread alloc] initWithTarget:[[MyTaskObject alloc] init] selector:@selector(performTask) object:nil];
[newThread start];

实现一个常驻线程,你可以使用 NSThread 来创建一个新的线程,并在该线程中运行一个循环,保持线程一直处于活动状态。下面是一个简单的示例代码,展示了如何实现一个常驻线程:

@interface ResidencyThread : NSObject
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL isCancelled;
@end

@implementation ResidencyThread

- (instancetype)init {
    self = [super init];
    if (self) {
        self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
        self.isCancelled = NO;
    }
    return self;
}

- (void)start {
    [self.thread start];
}

- (void)threadMain {
    @autoreleasepool {
        // 创建一个RunLoop,并将其添加到当前线程的NSRunLoop中
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

        // 在RunLoop中添加一个Source,保证RunLoop不会退出
        [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

        // 在循环中,检测标志位 isCancelled 是否被设置为 YES,如果是,则退出循环
        while (!self.isCancelled && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

        // 当循环结束时,线程即将退出
        NSLog(@"ResidencyThread is exiting.");
    }
}

- (void)cancel {
    self.isCancelled = YES;
    // 如果需要唤醒RunLoop以退出循环,可以向RunLoop发送一个事件
    [self.thread cancel];
}

@end


// 创建一个常驻线程对象
ResidencyThread *residencyThread = [[ResidencyThread alloc] init];

// 启动线程
[residencyThread start];

// 在需要结束线程时,调用 cancel 方法
[residencyThread cancel];


多线程与锁

1. @synchronized

@synchronized 块: @synchronized 是 Objective-C 提供的一种同步机制,用于确保在多线程环境下对共享资源的安全访问。使用 @synchronized 块可以确保同一时间只有一个线程可以进入临界区,从而避免多个线程同时访问共享资源。

@synchronized (self) {
    // 访问共享资源的代码
}

一般在创建单例对象的时候使用。

@interface MySingleton : NSObject
+ (instancetype)sharedInstance;
@end

@implementation MySingleton

static MySingleton *sharedInstance = nil;

+ (instancetype)sharedInstance {
    @synchronized(self) {
        if (sharedInstance == nil) {
            sharedInstance = [[self alloc] init];
        }
    }
    return sharedInstance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        // 初始化单例对象
    }
    return self;
}

@end

2. atomic

修饰属性关键字

对被修饰对象进行原子操作 (不负责使用)

@property(atomic) NSMutableArray *array;

self.array = [NSMutableArray array]; 能保证线程安全吗?

[self.array addObject: obj]; 能保证线程安全吗?

在上述情况下,@property(atomic) NSMutableArray *array; 修饰的 array 属性确实会提供一定程度的线程安全性,但是需要注意以下几点:

  1. 赋值操作: 当你使用 self.array = [NSMutableArray array]; 进行赋值时,由于是在一个原子操作中进行的,因此这个赋值操作是线程安全的。在多线程环境下,只有一个线程能够成功将数组赋值给 array 属性,其他线程会被阻塞等待。

  2. 添加元素操作: 当你使用 [self.array addObject:obj]; 添加元素时,虽然 array 属性是原子的,但是数组本身并不是原子的。这意味着在多线程环境下,虽然对 array 属性的访问是线程安全的,但是对数组中元素的添加操作并不是线程安全的。如果多个线程同时尝试添加元素,可能会导致数据竞争和不确定的行为。

因此,尽管 atomic 修饰符提供了一定程度的线程安全性,但并不能保证对数组中元素的添加操作的线程安全性。如果在多线程环境下需要对数组进行修改操作,建议使用其他线程安全的集合类,比如 NSLock、@synchronized 块、GCD 的并发队列等,来确保操作的线程安全性。

3. OSSpinLock

循环等待询问,不释放当前资源

用于轻量级数据访问,简单的 int值 +1/-1 操作。

OSSpinLock 是一种基于自旋的锁,它在早期的 macOS 和 iOS 开发中用于实现轻量级的线程同步。它的工作原理是在获取锁时,会一直循环检测锁的状态,直到获取到锁为止,因此被称为自旋锁。

然而,OSSpinLock 在高并发情况下存在一些问题,主要是由于自旋的特性会导致线程长时间占用 CPU 资源,造成线程饥饿和性能下降。因此,从 macOS 10.12 和 iOS 10.0 开始,OSSpinLock 已被标记为废弃,并且不推荐在新的代码中使用。

取而代之的是使用更现代的线程同步机制,比如 os_unfair_lock、dispatch_semaphore、pthread_mutex 等,这些锁更加智能和高效,能够更好地处理高并发场景,并且不会出现自旋锁可能导致的性能问题。

4. NSRecursiveLock

NSRecursiveLock 是 Foundation 框架中提供的一种互斥锁,与 NSLock 类似,但允许同一个线程在未释放锁之前多次对其加锁。这意味着,如果一个线程已经持有了 NSRecursiveLock,那么在释放之前,它可以多次调用 lock 方法而不会导致死锁。

@interface MyClass : NSObject
@property (nonatomic, strong) NSRecursiveLock *lock;
@end

@implementation MyClass

- (instancetype)init {
    self = [super init];
    if (self) {
        _lock = [[NSRecursiveLock alloc] init];
    }
    return self;
}

- (void)methodA {
    [self.lock lock];
    [self methodB];
    [self.lock unlock];
}

- (void)methodB {
    [self.lock lock];
    // other
    [self.lock unlock];
}

@end

5. NSLock

- (void)methodA {
    [lock lock];
    [self methodB];
    [lock unlock];
}

- (void)methodB {
    [lock lock];
    // other
    [lock unlock];
}

在这个示例中,methodA 和 methodB 都使用了同一个锁 lock 来进行加锁和解锁。这种情况下可能会导致死锁,因为 methodA 在调用 methodB 之前已经获取了锁,而 methodB 又试图再次获取同一个锁,这样就形成了锁的嵌套,可能导致线程在等待彼此释放锁的情况下产生死锁。

6. dispatch_semaphore_t

dispatch_semaphore_t 是 Grand Central Dispatch (GCD) 中的一种信号量机制,用于在多线程环境中进行线程同步。它可以用来控制并发访问资源的数量,从而实现对临界区的访问控制。

dispatch_semaphore_t 提供了两个主要的函数:

  1. dispatch_semaphore_create:用于创建一个信号量。需要传入一个初始值作为参数,表示可以同时访问的线程数量。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 初始值为 1
  1. dispatch_semaphore_waitdispatch_semaphore_signal:用于对信号量进行加锁和解锁操作。

  2. dispatch_semaphore_wait 会将信号量的值减 1,并等待直到信号量的值大于等于 0。如果信号量的值大于等于 0,那么函数会立即返回;如果信号量的值小于 0,那么当前线程会进入等待状态,直到信号量的值变为非负数为止。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量
  • dispatch_semaphore_signal 会将信号量的值加 1,唤醒一个或多个正在等待的线程。如果有线程正在等待信号量,那么其中一个线程会被唤醒并继续执行;如果没有线程在等待,那么信号量的值会增加,供后续的等待线程使用。
dispatch_semaphore_signal(semaphore); // 发送信号量

dispatch_semaphore_t 可以用于实现一些常见的同步操作,比如对资源的互斥访问、线程池的控制等。它是一种非常灵活且高效的线程同步机制,能够在多线程环境中保证代码的安全性和正确性。

@interface SharedResource : NSObject
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end

@implementation SharedResource

- (instancetype)init {
    self = [super init];
    if (self) {
        _dataArray = [NSMutableArray array];
        _semaphore = dispatch_semaphore_create(1); // 创建信号量,初始值为 1
    }
    return self;
}

- (void)addObject:(id)object {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); // 等待信号量
    [self.dataArray addObject:object];
    dispatch_semaphore_signal(self.semaphore); // 发送信号量
}

- (id)getObject {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); // 等待信号量
    id object = nil;
    if (self.dataArray.count > 0) {
        object = [self.dataArray lastObject];
        [self.dataArray removeLastObject];
    }
    dispatch_semaphore_signal(self.semaphore); // 发送信号量
    return object;
}

@end

在这个示例中,SharedResource 类封装了一个共享资源 dataArray,并使用 dispatch_semaphore_t 控制对该资源的并发访问。在 addObject: 方法和 getObject 方法中,先调用 dispatch_semaphore_wait 等待信号量,然后执行对共享资源的操作,最后调用 dispatch_semaphore_signal 发送信号量,释放资源。这样可以确保在同一时刻只有一个线程能够访问共享资源,从而避免了竞争条件和数据不一致的问题。

    // 创建一个初始值为 0 的信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    // 模拟三个并发请求
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 请求1
        NSLog(@"请求1开始");
        sleep(2); // 模拟耗时操作
        NSLog(@"请求1结束");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 请求2
        NSLog(@"请求2开始");
        sleep(1); // 模拟耗时操作
        NSLog(@"请求2结束");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 请求3
        NSLog(@"请求3开始");
        sleep(3); // 模拟耗时操作
        NSLog(@"请求3结束");
        dispatch_semaphore_signal(semaphore);
    });

    // 在独立的线程中等待信号量
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 等待所有请求完成
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        // 所有请求都完成后执行统一操作
        NSLog(@"所有请求都完成了");
    });