iOS-GCD的学习记录(3)

1.dispatch_suspend/dispatch_resume

这两个函数是用来暂停或继续队列运行的。当你想要往一个dispatch队列上添加多个任务,并且不想在全部任务添加完毕前执行任何一个任务,这个时候,你可以先让队列暂停运行,在你处理完后再让队列继续运行。


dispatch_suspend(queue);
dispatch_resume(queue);


要注意的一点是,暂停只会禁止执行还未开始执行的任务,已经处于执行中的任务无法暂停。在队列恢复运行后,未开始执行的任务就可以正常执行了。


2.Dispatch Semaphore


当你需要对一小部分间隔时间较短的任务做并发控制的时候,Semaphore(信号量)会比串行队列或者dispatch_barrier_async更好用。

前面我们有提到,并发读取或更新数据时很容易造成数据冲突或者程序崩溃。你可以用串行队列或者dispatch_barrier_async函数来避免这种问题。但是有时需要在很短的时间间隔里做一些并发控制。比如,下面这个例子。



dispatch_queue_t queue =
	dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i)
{
	dispatch_async(queue, ^{
		[array addObject:[NSNumber numberWithInt:i]];
	});
}

这个例子中,在一个全局并发队列上向数组中加入数据,会有多个线程同时操作数组。由于NSMutableArrary并不支持多线程,因此当多个线程同时操作数组的时候,可能会扰乱数组中的数据。这种情况,我们可以用dispatch semaphore来解决。

Dispatch semaphore是一个带有一个计数器的信号量。这就是多线程编程中所谓的计数器信号量。信号量有点像一个交通信号标志,标志起来的时候你可以走,标准落下的时候你要停下来。Dispatch semaphore用计数器来模拟这种标志。计数器为0,队列暂停执行新任务并等待信号;当计数器超过0后,队列继续执行新任务,并减少计数器。
要创建一个dispatch semaphore,用dispatch_semaphore_create函数。



dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

函数的参数是指计数器的初始计数。同样,因为函数名字中有”create”单词,用这个函数创建的对象,在你不需要它之后,要用dispatch_release释放掉。


dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数用于等待一个信号量。当信号量的计数器变成1(及以上)数字后,函数停止等待,使计数器减1,并返回。函数的第二个参数是指定的等待时间,dispatch_time_t类型。dispatch_semaphore_wait函数的返回值和dispatch_group_wait函数的返回值类型一样,不同的返回值代表了不同的结果。如下代码所示。




dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0)
{
/*
 * 信号量计数器大于等于1。
 * 或者在指定的等待时间超时前,信号量计数器变成了大于等于1的数字。
 * 计数器会自动减一
 *
 * 在这里,可以安全地运行你的任务了。
 */
}
else
{
/*
 * 因为信号量计数器是0,就只能等待超时了。
 * 
 * 这里处理等待超时的情况。
 */
}

dispatch_semaphore_wait函数返回0的时候,你就可以安全的运行你的任务了。在你的任务完成后,要调用dispatch_semaphore_signal函数,使信号量的计数器加1。(译注: 这就有点类似于,-1表示你占用一份资源;用完后,+1表示你用完了,空余了一份资源出来。其他等待的人收到信号后,就可以用你这份空余的资源了。)

现在来看看如何在前面那个例子中使用信号量。



dispatch_queue_t queue =
	dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/*
 * 创建一个信号量
 * 
 * 将初始计数器设置为1, 使得一次只能有1个线程访问NSMutableArray对象。
 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i)
{
	dispatch_async(queue, ^{
		
		/*
		 * 等待信号量
		 *
		 * 一直等待,直到信号量计数器大于等于1。
		 */
		dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

		/*
		 * 因为信号量计数器>=1,
		 * dispatch_semaphore_wait函数停止等待,计数器自动-1,流程继续
		 *
		 * 此例中,到这里的时候,计数器始终会变成0。
		 * 因为初始时为1,限定了一次只能有一个线程访问NSMutableArray对象。
		 * 
		 * 现在,在这里,你可以安全地更新数组了。
		 */
		[array addObject:[NSNumber numberWithInt:i]];

		/*
		 * 任务完成后,要调用dispatch_semaphore_signal函数,使计数器+1
		 * 如果还有其他线程在等待信号量,第一个进入等待状态的线程得到通知后就可以开始了运行了。
		 */
		dispatch_semaphore_signal(semaphore);
	)};
}

/*
 * 如果不再需要这个信号量了,要将它释放掉.
 */
dispatch_release(semaphore);

再次再附上另外一篇我找到的文章,也是非常不错的

当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore,对经常做unix开发的人来讲,我所介绍的内容可能就显得非常入门级了,信号量在他们的多线程开发中再平常不过了。

在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号

简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制。




dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_group_async(group, queue, ^{
        NSLog(@"%i",i);
        sleep(2);
        dispatch_semaphore_signal(semaphore);
    });
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(semaphore);

简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。


3.dispatch_once

dispatch_once函数用于确保一个任务在整个程序运行过程中,只会被执行一次。下面的代码是初始化一个对象的是的典型做法。



static int initialized = NO;
if (initialized == NO)
{
	/*
	 * 初始化操作
	 */
	
	initialized = YES;
}

如果用dispatch_once函数来实现就更简洁了。



static dispatch_once_t pred;
dispatch_once(&pred, ^{
	/*
 	 * 初始化操作 
	 */
});

4.Dispatch I/O

读取一个大文件的时候,你可能会想到,如果将文件分成若干小块再用全局队列并发读取,这样会比正常情况下一次性读取整个文件快很多。对于目前的I/O硬件来说,并发读取可能确实会比单线程读取要快。要想获得更快的读取速度,可以使用Dispatch I/O和Dispatch Data。当你使用Dispatch I/0读写文件的时候,文件会被分成某个固定大小的文件块,你可以在一个全局队列上访问它们。




dispatch_async(queue, ^{/* 读取文件的 0 至 8191 字节。 */});
dispatch_async(queue, ^{/* 读取文件的 8192 至 16383 字节。 */});
dispatch_async(queue, ^{/* 读取文件的 16384 至 24575 字节 */});
dispatch_async(queue, ^{/* 读取文件的 24576 至 32767 字节 */});
dispatch_async(queue, ^{/* 读取文件的 32768 至 40959 字节 */});
dispatch_async(queue, ^{/* 读取文件的 40960 至 49151 字节 */});
dispatch_async(queue, ^{/* 读取文件的 49152 至 57343 字节 */});
dispatch_async(queue, ^{/* 读取文件的 57344 至 65535 字节 */});

如代码中所示,读取操作会分块进行。用Dispatch Data很容易将读取到的数据组合起来。如下,苹果官方的dispatch I/O和dispatch data例子。




pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
});

*out_fd = fdpair[1];

dispatch_io_set_low_water(pipe_channel, SIZE_MAX);

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
	if (err == 0)
	{
		size_t len = dispatch_data_get_size(pipedata);
		if (len > 0)
		{
			const char *bytes = NULL;
			char *encoded;
			dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
			encoded = asl_core_encode_buffer(bytes, len);
			asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
			free(encoded);
			_asl_send_message(NULL, merged_msg, -1, NULL);
			asl_msg_release(merged_msg);
			dispatch_release(md);
		}
}

if (done)
{
	dispatch_semaphore_signal(sem);
	dispatch_release(pipe_channel);
	dispatch_release(pipe_q);
}
});

这是来自苹果的系统日志API(Libc-763.11 gen/asl.c)的源代码。dispatch_io_create函数创建了一个dispatch I/O。它指定了一个会在发生错误的时候被执行的block,以及执行block的队列。dispatch_io_set_low_water函数指定了每次读取操作最多可以读取的数据长度(数据会按这个尺寸分块)。dispatch_io_read函数在全局队列上开启读取操作。每当一块数据被读取后,数据作为参数会被传递给dispatch_io_read的回调函数,因此,block就可以扫描或组合数据了。

如果你想读取文件的时候速度快些,就试试用dispatch I/O吧。


参考文章:在CGD中快速实现多线程的并发控制 GCD使用详解

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值