目录

应用开发

编写app/src/main/cpp/native-lib.cpp。以下给出多线程库的几个典型场景示例。

基础使用

dispatch_async和dispatch_sync是multithread-lib最基础的接口,用于异步或同步地提交一个任务到某个队列上。异步和同步的区别是接口会立即返回还是等待提交的任务执行完成后返回。
#include <dispatch/dispatch.h>
#include <android/log.h>
#define log(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "dispatch", fmt, ##args)

void sample(void)
{
	dispatch_queue_t my_q = dispatch_queue_create("example", NULL);

	dispatch_async(my_q, ^{
		log("Here is job A\n");
	});
	log("Job A may or may not finish at this moment\n");

	dispatch_sync(my_q, ^{
		log("Here is job B\n");
	});
	log("Job B must have finished\n");
}

队列区分为串行队列和并发队列。上面代码定义了一个串行队列my_q,意味着提交到my_q上的任务A和任务B是串行执行的,即B会等待A执行完毕之后才开始执行。

如果把my_q定义成一个并发队列,那么A和B可以并行执行。这种情况下如果需要在函数退出的时候确保A和B都执行完了,可以最后在并发队列上同步地插入一个barrier任务。

dispatch_queue_t my_q = dispatch_queue_create("example", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(my_q, ^{
	log("Here is job A\n");
});
log("Job A may or may not finish at this moment\n");

dispatch_sync(my_q, ^{
	log("Here is job B\n");
});
log("Job B must have finished\n");

dispatch_barrier_sync(my_q ^{
	log("Ensure job A has finished\n");
});

异步执行

在很多应用场景下,我们希望把某个耗时的任务A异步执行,以不阻塞主流程其它任务。在某个时刻需要等待任务A执行完毕或者获得A执行完毕的通知。

// Get the global concurrent queue at default priority.
dispatch_queue_t q = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
// Create a group to manage jobs.
dispatch_group_t grp = dispatch_group_create();

dispatch_group_async(grp, q, ^{
	// Some asynchronous heavy work.
});

// Do something else.

dispatch_group_async(grp, q, ^{
	// Another asynchronous work.
});

// At a particular point you may want to ensure previous
// asynchronous work have been finished or otherwise wait
// for them to finish.
dispatch_group_wait(grp, DISPATCH_TIME_FOREVER);
// Don't forget to release grp when it is no longer needed.
dispatch_release(grp);

// Go on.

上面代码中是主流程同步等待前面提交的任务执行完成。也可以通过dispatch_group_notify异步地获得任务执行完成的通知,即执行完后自动再提交某个任务到某个队列上。

并行计算

使用并发队列可以让提交的任务并行执行。如果是可拆分成多个子任务的并行计算,那么通常使用dispatch_apply接口提交,此时可以充分利用多核CPU算力获得最快的执行速度。下面提供了一个使用莱布尼茨公式计算π的示例,完整工程见Codelab。

double calculate_pi(void)
{
	__block double pi = 0;
	int n = 1000000;
	int threads = 8;
	dispatch_queue_t accumulator = dispatch_queue_create("Compute pi", NULL);

	dispatch_apply(threads, DISPATCH_APPLY_AUTO, ^(size_t idx){
		int start = idx * (n / threads);
		int end = (idx + 1) * (n / threads);
		double sum = 0;
		for (int k = start; k < end; k++) {
			double flag = (k & 1) ? -1.0 : 1.0;
			sum += flag / (2 * k + 1);
		}

		dispatch_sync(accumulator, ^{
			pi += sum;
		});
	});

	dispatch_release(accumulator);
	return pi * 4;
}

互斥访问

提交到串行队列上的任务是串行执行的,而且multithread-lib中提交任务的开销非常微小,因此使用串行队列可以完全代替锁来保护临界区资源的互斥访问,只要把某个资源的访问动作放在同一个串行队列上。

相对使用锁而言,由于具备同步和异步的灵活性,因此可以避免多线程情况下锁竞争的问题。此外在多线程编程的架构上也非常统一,无论是并行执行还是互斥保护都是使用提交任务到某个队列上的形式,具备较好的可读性。

std::map<std::string, int> count;
dispatch_queue_t count_q = dispatch_queue_create("Access count", NULL);

dispatch_sync(count_q, ^{ /* Manipulate count. */ });
dispatch_async(count_q, ^{ /* Manipulate count asynchronously. */ });

另外,注意在C语言Block实现中默认对外部自由变量以拷贝方式使用(只读)。因此像上述的一个map可以在Block内直接操作,而如果count是一个unsigned int类型,即Block中需要修改外部变量自身值的时候,需要在变量前加前缀“__block”。更多内容请参见ClangBlockLanguageSpec。

__block unsigned int count;