基本概念
进程和线程的区别
- 进程是指系统中正在运行的一个应用程序, 每个进程之间是相互独立的
- 一个进程中可以有多条线程, 进程的所有任务都在线程中执行的
进程的状态
- 新建
- 就绪 : 线程对象加入线程池中等待 CPU 调度
- 运行 : CPU负责调度线程中线程的执行, 线程执行完成前, 状态可能在就绪和运行之间来回切换
- 阻塞 : 满足某个预定条件, 使用休眠或锁, 阻塞线程执行
- 死亡 : 线程执行完毕, 或者内部中止执行线程对象
线程安全
多个线程同时访问一块资源, 容易引发数据错乱和数据安全
- 互斥锁 : 新线程访问时, 发现其他线程正在执行锁定的代码, 新线程会进入休眠
NSLock
pthread_mutex
@synchronized
自旋锁
忙等的锁, 新线程会用死循环的方式, 一直等待锁定的代码执行完成, 数据量少的时候用条件锁
不满足就休眠, 资源分配到了, 条件锁打开, 进程继续运行, 例如:NSConditionLock读写锁
用于解决多线程对公共资源读写问题。 读操作可以并发重入, 写操作是互斥的
// 递归锁
NSRecursiveLock
pthread_mutex(PTHREAD_MUTEX_RECURSIVE)
- 信号量
面试题
- 在不同的队列中, 执行100次dispatch_async 会创建多少个线程
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrent = dispatch_queue_create("councurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++) {
dispatch_async(serial, ^{
NSLog(@" 1 ----%@ ",[NSThread currentThread]);
});
dispatch_async(concurrent, ^{
NSLog(@" 2----%@ ",[NSThread currentThread]);
});
dispatch_async(global, ^{
NSLog(@" 3----%@ ",[NSThread currentThread]);
});
}
串行队列只会创建一个线程, global 和 自定义 concurrent 队列会创建多个线程
- dispatch_once 的底层实现
void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) {
struct _dispatch_once_waiter_s * volatile *vval = (struct _dispatch_once_waiter_s**)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;
if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
_dispatch_client_callout(ctxt, func);
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
tail = &dow;
while (tail != tmp) {
while (!tmp->dow_next) {
_dispatch_hardware_pause();
}
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else {
dow.dow_sema = _dispatch_get_thread_semaphore();
for (;;) {
tmp = *vval;
if (tmp == DISPATCH_ONCE_DONE) {
break;
}
dispatch_atomic_store_barrier();
if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
第一次调用: 此时外部传进来的 onceToken 还是空指针,所以 vval 为 NULL,if 判断成立。首先执行 block,然后让将 vval 的值设为 DISPATCH_ONCE_DONE 表示任务已经完成,同时用 tmp 保存先前的 vval。此时,dow 也为空,因此 while 判断不成立,代码执行结束。
同一线程第二次调用: 由于 vval 已经变成了 DISPATCH_ONCE_DONE,因此 if 判断不成立,进入 else 分支的 for 循环。由于 tmp 就是 DISPATCH_ONCE_DONE,所以循环退出,没有做任何事。
多个线程同时调用: 由于 if 判断中是一个原子性操作,所以必然只有一个线程能进入 if 分支,其他的进入 else 分支。由于其他线程在调用函数时,vval 还不是 DISPATCH_ONCE_DONE,所以进入到 for 循环的后半部分。这里构造了一个链表,链表的每个节点上都调用了信号量的 wait 方法并阻塞,而在 if 分支中,则会依次遍历所有的节点并调用 signal 方法,唤醒所有等待中的信号量。
- dispatch_barrier_async 底层实现
void dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
dispatch_continuation_t dc;
dc = fastpath(_dispatch_continuation_alloc_cacheonly());
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
dc->dc_func = func;
dc->dc_ctxt = ctxt;
_dispatch_queue_push(dq, dc);
}
static _dispatch_thread_semaphore_t _dispatch_queue_drain(dispatch_queue_t dq) {
while (dq->dq_items_tail) {
/* ... */
if (!DISPATCH_OBJ_IS_VTABLE(dc) && (long)dc->do_vtable & DISPATCH_OBJ_BARRIER_BIT) {
if (dq->dq_running > 1) {
goto out;
}
} else {
_dispatch_continuation_redirect(dq, dc);
continue;
}
}
out:
/* 不完整的 drain,需要清理现场 */
return sema; // 返回空的信号量
}
void _dispatch_queue_invoke(dispatch_queue_t dq) {
_dispatch_thread_semaphore_t sema = _dispatch_queue_drain(dq);
if (sema) {
_dispatch_thread_semaphore_signal(sema);
} else if (tq) {
return _dispatch_queue_push(tq, dq);
}
}
do_vtable 设定了标志位 DISPATCH_OBJ_BARRIER_BIT
, 从队列中取出任务执行的时候遇见这个标志位立即停止, 会终止循环, 返回一个空的信号量, 然后调用 _dispatch_queue_push
手动把这个任务添加进去
参考资料: http://ios.jobbole.com/88638/
- @synchronized 底层实现
- 传入的对象在 block 里面被释放或者被置为 nil 会怎样 ?
主要是调用了 objc_sync_enter 和 objc_sync_exit 方法
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
/**
* Begin synchronizing on 'obj'.
* Allocates recursive pthread_mutex associated with 'obj' if needed.
*
* @param obj The object to begin synchronizing on.
*
* @return OBJC_SYNC_SUCCESS once lock is acquired.
*/
OBJC_EXPORT int objc_sync_enter(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0);
/**
* End synchronizing on 'obj'.
*
* @param obj The objet to end synchronizing on.
*
* @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
*/
OBJC_EXPORT int objc_sync_exit(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0);
typedef struct SyncData {
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount;
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
// Use multiple parallel lists to decrease contention among unrelated objects.
#define COUNT 16
#define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1))
#define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
#define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
static SyncList sDataLists[COUNT];
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = recursive_mutex_lock(&data->mutex);
require_noerr_string(result, done, "mutex_lock failed");
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
done:
return result;
}
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = recursive_mutex_unlock(&data->mutex);
require_noerr_string(result, done, "mutex_unlock failed");
} else {
// @synchronized(nil) does nothing
}
done:
if ( result == RECURSIVE_MUTEX_NOT_LOCKED )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}
定义哈希算法将传入的对象映射到数组上的一个下标, 对象指针在内存的地址转变成无符号整形并有移5位, 再跟 COUNT 做 & 运算, 这样结果不会超出数组大小
使用递归锁 mutex 来做同步, @synchronized(nil) 不起任何作用
如果在block 里面传入了nil, 将会从代码中移走线程安全, 调用objc_sync_exit 方法时候, 不做解锁处理
- dispatch_async 的底层实现
队列是用来提交 block 的对象, 按照先入先出的顺序进行处理, GCD 底层会维护一个线程池, 用来执行这些 bock。
void dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
dispatch_continuation_t dc;
if (dq->dq_width == 1) {
return dispatch_barrier_async_f(dq, ctxt, func);
}
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
dc->dc_func = func;
dc->dc_ctxt = ctxt;
if (dq->do_targetq) {
return _dispatch_async_f2(dq, dc);
}
_dispatch_queue_push(dq, dc);
}
如果是串行队列 dq_with == 1, 调用 dispatch_barrier_async_f 函数处理, 如果有 do_targetq 则进行转发, 否则调用 _dispatch_queue_push 入队
用链表保存所有提交的 block,然后在底层线程池中,依次取出 block 并执行