Skip to content

多线程

FFur edited this page Sep 2, 2018 · 1 revision

多线程

主线程

  • 所有的UI交互都发生在主线程,所以为了用户体验,不想让任何耗时操作发生在主线程。
  • 使用主线程进行所有的UI同步。优先响应用户的操作(点击,手势等)
  • 其他所有的操作,可以放在子线程中执行

获取主队列

dispatch_queue_t mainQ = dispatch_get_main_queue();

//NSObject method

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait{};

dispatch_async (dispatch_get_main_queue(), ^{ /* call aMethod */ });

创建一个队列

dispatch_queue_t otherQ = dispatch_queue_create("name", NULL);
//第二个参数: In OS X v10.7 and later or iOS 4.3 and later, specify DISPATCH_QUEUE_SERIAL (or NULL) to create a serial queue or specify DISPATCH_QUEUE_CONCURRENT to create a concurrent queue. In earlier versions, you must specify NULL for this parameter.
  • 常见多线程在项目中的应用?
  • 多线程的死锁
  • 控制并发线程的数量: 多线程的信号量dispatch_semaphore
  • 线程的异步
  • 任务进行分块: dispatch_group_notify.
  • iOS使用dispatch_group实现分组并发网络请求

线程死锁

案例一:

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

结果,控制台输出:

 1

分析:

  • dispatch_sync表示是一个同步线程;
  • dispatch_get_main_queue表示运行在主线程中的主队列;
  • 任务2是同步线程的任务。
  • 首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,问题来了:

案例二

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

结果,控制台输出:

1
2 
3

分析:

首先执行任务1,接下来会遇到一个同步线程,程序会进入等待。等待任务2执行完成以后,才能继续执行任务3。从dispatch_get_global_queue可以看出,任务2被加入到了全局的并行队列中,当并行队列执行完任务2以后,返回到主队列,继续执行任务3。

例子4

- (void)viewDidLoad
{
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    NSLog(@"=================1");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"=================2");

    });
    NSLog(@"=================3");

    
});

}

程序会完成执行,为什么不会出现死锁。

首先: async 在主线程中 创建了一个异步线程 加入全局并发队列,async 不会等待block 执行完成,立即返回,

1,async 立即返回, viewDidLoad 执行完毕,及主线程执行完毕。 2,同时,全局并发队列立即执行异步 block , 打印 1, 当执行到 sync 它会等待 block 执行完成才返回, 及等待 dispatch_get_main_queue() 队列中的 mianThread 执行完成, 然后才开始调用block 。

因为1 和 2 几乎同时执行,因为2 在全局并发队列上, 2 中执行到sync 时 1 可能已经执行完成或 等了一会,mainThread 很快退出, 2 等已执行后续内容。

如果阻塞了主线程,2 中的sync 就无法执行啦,mainThread 永远不会退出, sync 就永远等待着,

NSOperation

NSOperation和NSOperationQueue是Apple基于GCD封装的一套面向对象的API.

使用Operation的优势如下:

  • 可以给代码块添加completionBlock, 在任务完成以后自己调用. 相对于GCD代码更简洁.(类似于GCD的dispatch_block_wait/dispatch_block_notify)
  • operation之间可以添加依赖关系. (addDependency)
  • 设置operation的优先级.(类似与gcd block的qos)
  • 方便的设置operation取消操作(gcd的dispatch_block_cancel)
  • 使用KVO观察对operation状态的监听: isExcuting, isFinished, isCancelled.
  1. NSOperation就是操作, 类似GCD中的block.通常有NSInvocationOperation、NSBlockOperation, 以及自定义NSOperation三种.

  2. NSOperationQueue 是操作队列, 即存放operation的队列. NSOperationQueue将Operation添加到队列中以后, Operation首先进入ready状态(是否ready取决与不同operation之间的依赖dependency), 如果ready状态的operation会开始按照operation的优先级, 顺序被调用执行.操作队列通过maxConcurrentOperationCount属性, 控制并发,串行.(类似于GCD的并行和串行队列).

NSInvocationOperation调用start执行

调用实例:

/**
 1\. start---<NSThread: 0x604000071440>{number = 1, name = main}
 2\. 1---<NSThread: 0x604000071440>{number = 1, name = main}
 3\. 2---<NSThread: 0x604000071440>{number = 1, name = main}
 4\. end--- <NSThread: 0x604000071440>{number = 1, name = main}
 */
- (void) operationStartDemo2 {
    NSLog(@"start---%@", [NSThread currentThread]);

    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];
    NSLog(@"end--- %@", [NSThread currentThread]);
}

- (void)task2 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"%d---%@", i,[NSThread currentThread]); // 打印当前线程
    }
}

NSBlockOperation

/**
 1\. start---<NSThread: 0x600000070d00>{number = 1, name = main}
 2\. 0---<NSThread: 0x600000070d00>{number = 1, name = main}
 3\. 1---<NSThread: 0x600000070d00>{number = 1, name = main}
 4\. end--- <NSThread: 0x600000070d00>{number = 1, name = main}
 */
- (void)operationStartDemo3 {
    NSLog(@"start---%@", [NSThread currentThread]);
    // 1.创建blockOperation
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"%d---%@", i,[NSThread currentThread]); // 打印当前线程
        }
    }];
    // 2.调用 start 方法开始执行操作
    [op start];
    NSLog(@"end--- %@", [NSThread currentThread]);
}

如果NSBlockOperation中只有一个任务, 那么调用start在当前线程中同步执行.

/**
 1\. start---<NSThread: 0x60400006c480>{number = 1, name = main}
 2\. 4---<NSThread: 0x60400027b500>{number = 4, name = (null)}
 3\. 2---<NSThread: 0x60400006c480>{number = 1, name = main}
 4\. 1---<NSThread: 0x60400027b5c0>{number = 5, name = (null)}
 5\. 3---<NSThread: 0x60000026d280>{number = 3, name = (null)}
 6\. 1---<NSThread: 0x60400027b5c0>{number = 5, name = (null)}
 7\. 2---<NSThread: 0x60400006c480>{number = 1, name = main}
 8\. 4---<NSThread: 0x60400027b500>{number = 4, name = (null)}
 9\. 3---<NSThread: 0x60000026d280>{number = 3, name = (null)}
 10 end--- <NSThread: 0x60400006c480>{number = 1, name = main}
 */
- (void)blockOperationAddOperationDemo {
    NSLog(@"start---%@", [NSThread currentThread]);
    // 1.创建blockOperation
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.添加额外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];
    NSLog(@"end--- %@", [NSThread currentThread]);
}

NSOperationQueue

  • NSOperation 可以调用 start 方法来执行任务,但默认是同步执行的
  • 如果将 NSOperation 添加到 NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

有两种方式将Operation添加到NSOperationQueue中:

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

下面是通过多重方式将各种NSOperation添加到NSOperationQueue的demo:

/**
 1\. start---<NSThread: 0x600000262840>{number = 1, name = main}
 2\. end---<NSThread: 0x600000262840>{number = 1, name = main}
 3\. 1---<NSThread: 0x600000661ec0>{number = 3, name = (null)}
 4\. 4---<NSThread: 0x604000267800>{number = 5, name = (null)}
 5\. 3---<NSThread: 0x6000006620c0>{number = 4, name = (null)}
 6\. 2---<NSThread: 0x604000267b80>{number = 6, name = (null)}
 7\. 4---<NSThread: 0x604000267800>{number = 5, name = (null)}
 8\. 1---<NSThread: 0x600000661ec0>{number = 3, name = (null)}
 9\. 3---<NSThread: 0x6000006620c0>{number = 4, name = (null)}
 10 2---<NSThread: 0x604000267b80>{number = 6, name = (null)}
 */
-(void)operationQueueDemo1{
    NSOperationQueue *oq = [[NSOperationQueue alloc]init];
    NSLog(@"start---%@", [NSThread currentThread]);

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
        }
    }];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    PPOperation *op3 = [PPOperation new];
    [oq addOperation:op1];
    [oq addOperation:op2];
    [oq addOperation:op3];
    [oq addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
        }
    }];
    NSLog(@"end---%@", [NSThread currentThread]);
}

/**
 1\. start---<NSThread: 0x600000074c00>{number = 1, name = main}
 2\. end---<NSThread: 0x600000074c00>{number = 1, name = main}
 3\. 4---<NSThread: 0x600000464880>{number = 3, name = (null)}
 4\. 4---<NSThread: 0x600000464880>{number = 3, name = (null)}

 */
-(void)operationQueueDemo2{
    NSOperationQueue *oq = [[NSOperationQueue alloc]init];
    NSLog(@"start---%@", [NSThread currentThread]);
    [oq addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
        }
    }];
    NSLog(@"end---%@", [NSThread currentThread]);
}

多任务分块

dispatch_group_enter(group)和dispatch_group_leave(group),这种方式使用更为灵活,enter和leave必须配合使用,有几次enter就要有几次leave

dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求1
        [网络请求:{
        成功:dispatch_group_leave(group);
        失败:dispatch_group_leave(group);
}];
    });
    dispatch_group_enter;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求2
        [网络请求:{
        成功:dispatch_group_leave;
        失败:dispatch_group_leave;
}];
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求3
        [网络请求:{
        成功:dispatch_group_leave(group);
        失败:dispatch_group_leave(group);
}];
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成,刷新界面");
    });
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

参考iOS使用dispatch_group实现分组并发网络请求 - 简书

信号量

dispatch_semaphore

信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。

系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理

  • 如果一个线程等待一个信号量dispatch_semaphore_wait,线程会被阻塞,直到计数器>0,此时开始运行,并且对信号量减1。
  • 0表示没有资源, dispatch_semaphore_create会返回NULL,调用dispatch_semaphore_wait会立即等待。
  • 当一个信号量被通知dispatch_semaphore_signal,是发送一个信号,自然会让信号总量加1; Calls to dispatch_semaphore_signal must be balanced with calls to dispatch_semaphore_wait. Attempting to dispose of a semaphore with a count lower than value causes an EXC_BAD_INSTRUCTION exception.对 dispatch_semaphore_signal 的呼叫必须与对 dispatch_semaphore_wait 的呼叫保持平衡。试图用低于值的计数来处理信号量会导致 EXC_BAD_INSTRUCTION 异常。

参考浅谈GCD中的信号量 - 简书

创建信号量
// 值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL
dispatch_semaphore_t dispatch_semaphore_create(long value);
等待
// 返回0:表示正常。返回非0:表示等待时间超时
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
信号通知
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

这样我们就可以根据 初始值 ,来控制可以有多少个并发的线程在运行。关于信号量,可以用停车位来比喻,如果停车场有5个停车位,都停满了,如果此时来了第6辆车,就需要等待,信号量的值就相当于剩余的车位的数量。dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。

dispatch_semaphore_wait中的参数timeout表示超时时间,如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。可取值为:DISPATCH_TIME_NOWDISPATCH_TIME_FOREVER,我们也可以自己设置一个dispatch_time_t的时间值,表示超时时间为这个时间之后。

  • DISPATCH_TIME_NOW:超时时间为0,表示忽略信号量,直接运行
  • ** DISPATCH_TIME_FOREVER**:超时时间为永远,表示会一直等待信号量为正数,才会继续运行

例子

信号量的初始值设置为:1,即最多只能又一个线程在run,可以验证一下运行结果,除去最开始的三个,后面的是每三秒打印一个,并且保证运行的顺序按照添加的顺序。

如果把创建信号量的值设置为4,即最多可以有4个线程同时运行,来看一下运行结果,是每三秒同时打印四个,且顺序不能保证。

其他

GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:

  • dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
  • dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
  • dispatch_time():延迟一定的时间后执行。
  • dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
  • dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。

NSLock

iOS中对于资源抢占的问题可以使用同步锁NSLock来解决,使用时把需要加锁的代码(以后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间,一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。需要注意的是lock和unlock之间的”加锁代码“应该是抢占资源的读取和修改代码,不要将过多的其他操作代码放到里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了。

另外,在上面的代码中”抢占资源“_imageNames定义成了成员变量,这么做是不明智的,应该定义为“原子属性”。对于被抢占资源来说将其定义为原子属性是一个很好的习惯,因为有时候很难保证同一个资源不在别处读取和修改。nonatomic属性读取的是内存数据(寄存器计算好的结果),而atomic就保证直接读取寄存器的数据,这样一来就不会出现一个线程正在修改数据,而另一个线程读取了修改之前(存储在内存中)的数据,永远保证同时只有一个线程在访问一个属性。

下面的代码演示了如何使用NSLock进行线程同步:

KCMainViewController.h

//
//  KCMainViewController.h
//  MultiThread
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #import <UIKit/UIKit.h> @interface KCMainViewController : UIViewController

@property (atomic,strong) NSMutableArray *imageNames;
@end

KCMainViewController.m

//
//  线程同步
//  MultiThread
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 #define IMAGE_COUNT 9

@interface KCMainViewController (){
    NSMutableArray *_imageViews;
    NSLock *_lock;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
} #pragma mark 界面布局
-(void)layoutUI{ //创建多个图片控件用于显示图片 _imageViews=[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];

        }
    }

    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加载图片" forState:UIControlStateNormal]; //添加方法 [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button]; //创建图片链接 _imageNames=[NSMutableArray array]; for (int i=0; i<IMAGE_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    } //初始化锁对象 _lock=[[NSLock alloc]init];

} #pragma mark 将图片显示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
    UIImage *image=[UIImage imageWithData:data];
    UIImageView *imageView= _imageViews[index];
    imageView.image=image;
} #pragma mark 请求图片数据
-(NSData *)requestData:(int )index{
    NSData *data;
    NSString *name; //加锁 [_lock lock]; if (_imageNames.count>0) {
        name=[_imageNames lastObject];
        [_imageNames removeObject:name];
    } //使用完解锁 [_lock unlock]; if(name){
        NSURL *url=[NSURL URLWithString:name];
        data=[NSData dataWithContentsOfURL:url];
    } return data;
} #pragma mark 加载图片
-(void)loadImage:(NSNumber *)index{ int i=[index integerValue]; //请求数据 NSData *data= [self requestData:i]; //更新UI界面,此处调用了GCD主线程队列的方法 dispatch_queue_t mainQueue= dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        [self updateImageWithData:data andIndex:i];
    });
} #pragma mark 多线程下载图片
-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT;

    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //创建多个线程用于填充图片 for (int i=0; i<count; ++i) { //异步执行队列任务 dispatch_async(globalQueue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
    }

}
@end

@synchronized代码块

使用@synchronized解决线程同步问题相比较NSLock要简单一些,日常开发中也更推荐使用此方法。首先选择一个对象作为同步对象(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。下面的代码演示了如何使用@synchronized进行线程同步:

-(NSData *)requestData:(int )index{
    NSData *data;
    NSString *name; //线程同步 @synchronized(self){ if (_imageNames.count>0) {
            name=[_imageNames lastObject];
            [NSThread sleepForTimeInterval:0.001f];
            [_imageNames removeObject:name];
        }
    } if(name){
        NSURL *url=[NSURL URLWithString:name];
        data=[NSData dataWithContentsOfURL:url];
    } return data;
}

扩展--使用GCD解决资源抢占问题

在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)。GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。

//
//  GCD实现多线程--消息信号
//  MultiThread
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
#define IMAGE_COUNT 9

@interface KCMainViewController (){
    NSMutableArray *_imageViews;
    NSLock *_lock;
    dispatch_semaphore_t _semaphore;//定义一个信号量
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self layoutUI];
}

#pragma mark 界面布局
-(void)layoutUI{
    //创建多个图片控件用于显示图片
    _imageViews=[NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加载图片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    //创建图片链接
    _imageNames=[NSMutableArray array];
    for (int i=0; i<IMAGE_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    }
    
    /*初始化信号量
     参数是信号量初始值
     */
    _semaphore=dispatch_semaphore_create(1);
    
}

#pragma mark 将图片显示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
    UIImage *image=[UIImage imageWithData:data];
    UIImageView *imageView= _imageViews[index];
    imageView.image=image;
}

#pragma mark 请求图片数据
-(NSData *)requestData:(int )index{
    NSData *data;
    NSString *name;
    
    /*信号等待
     第二个参数:等待时间
     */
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    if (_imageNames.count>0) {
        name=[_imageNames lastObject];
        [_imageNames removeObject:name];
    }
    //信号通知
    dispatch_semaphore_signal(_semaphore);

    
    if(name){
        NSURL *url=[NSURL URLWithString:name];
        data=[NSData dataWithContentsOfURL:url];
    }
    
    return data;
}

#pragma mark 加载图片
-(void)loadImage:(NSNumber *)index{
    int i=[index integerValue];
    //请求数据
    NSData *data= [self requestData:i];
    //更新UI界面,此处调用了GCD主线程队列的方法
    dispatch_queue_t mainQueue= dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        [self updateImageWithData:data andIndex:i];
    });
}

#pragma mark 多线程下载图片
-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
//    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //这里创建一个并发队列(使用全局并发队列也可以)
    dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i=0; i<count; i++) {
        dispatch_async(queue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
    }
}

@end

运行效果与前面使用同步锁是一样的。

参考