from:http://www.tanhao.me/tag/nslock
为什么需要使用锁,当然熟悉多线程的你,自然不会对它觉得陌生。
那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢?
今天一起来探讨一下Objective-C中几种不同方式实现的锁,在这之前我们先构建一个测试用的类,假想它是我们的一个共享资源,method1与method2是互斥的,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@
implementation
TestObj
-
(
void
)
method1
{
NSLog
(
@
"%@"
,
NSStringFromSelector
(
_cmd
)
)
;
}
-
(
void
)
method2
{
NSLog
(
@
"%@"
,
NSStringFromSelector
(
_cmd
)
)
;
}
@
end
|
1.使用NSLock实现的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//主线程中
TestObj *
obj
=
[
[
TestObj
alloc
]
init
]
;
NSLock *
lock
=
[
[
NSLock
alloc
]
init
]
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
[
lock
lock
]
;
[
obj
method1
]
;
sleep
(
10
)
;
[
lock
unlock
]
;
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
sleep
(
1
)
;
//以保证让线程2的代码后执行
[
lock
lock
]
;
[
obj
method2
]
;
[
lock
unlock
]
;
}
)
;
|
看到打印的结果了吗,你会看到线程1锁住之后,线程2会一直等待走到线程1将锁置为unlock后,才会执行method2方法。
NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常所使用的,除lock和unlock方法外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),刚并不会阻塞线程,并返回NO。lockBeforeDate:方法会在所指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
2.使用synchronized关键字构建的锁
当然在Objective-C中你还可以用@synchronized指令快速的实现锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//主线程中
TestObj *
obj
=
[
[
TestObj
alloc
]
init
]
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
@
synchronized
(
obj
)
{
[
obj
method1
]
;
sleep
(
10
)
;
}
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
sleep
(
1
)
;
@
synchronized
(
obj
)
{
[
obj
method2
]
;
}
}
)
;
|
@synchronized指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj)改为@synchronized(other),刚线程2就不会被阻塞,@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
3.使用C语言的pthread_mutex_t实现的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//主线程中
TestObj *
obj
=
[
[
TestObj
alloc
]
init
]
;
__block
pthread_mutex_t
mutex
;
pthread_mutex_init
(
&
mutex
,
NULL
)
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
pthread_mutex_lock
(
&
mutex
)
;
[
obj
method1
]
;
sleep
(
5
)
;
pthread_mutex_unlock
(
&
mutex
)
;
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
sleep
(
1
)
;
pthread_mutex_lock
(
&
mutex
)
;
[
obj
method2
]
;
pthread_mutex_unlock
(
&
mutex
)
;
}
)
;
|
pthread_mutex_t定义在pthread.h,所以记得#include <pthread.h>
4.使用GCD来实现的”锁”
以上代码构建多线程我们就已经用到了GCD的dispatch_async方法,其实在GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”(从本质意义上讲,信号量与锁是有区别,具体差异参加信号量与互斥锁之间的区别):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//主线程中
TestObj *
obj
=
[
[
TestObj
alloc
]
init
]
;
dispatch_semaphore_t
semaphore
=
dispatch_semaphore_create
(
1
)
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
dispatch_semaphore_wait
(
semaphore
,
DISPATCH_TIME_FOREVER
)
;
[
obj
method1
]
;
sleep
(
10
)
;
dispatch_semaphore_signal
(
semaphore
)
;
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
sleep
(
1
)
;
dispatch_semaphore_wait
(
semaphore
,
DISPATCH_TIME_FOREVER
)
;
[
obj
method2
]
;
dispatch_semaphore_signal
(
semaphore
)
;
}
)
;
|
至于代码产生的效果当然和上一例是一模一样的,关于信号机制,熟悉C编程的你肯定也不会陌生的,关于GCD中更多关于dispatch_semaphore_t的信息,可以跳转到本博客的这一往篇文章:GCD介绍(三): Dispatch Sources
好了,以上就是我所列举了几种方式来实现锁,当然锁大多数情况下也是配合多线程一起使用的,关于多线程编程,我这儿就不赘述了。
在上一文中,我们已经讨论过用Objective-C锁几种实现(跳转地址),也用代码实际的演示了如何通过构建一个互斥锁来实现多线程的资源共享及线程安全,今天我们继续讨论锁的一些高级用法。
1.NSRecursiveLock递归锁
平时我们在代码中使用锁的时候,最容易犯的一个错误就是造成死锁,而容易造成死锁的一种情形就是在递归或循环中,如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//主线程中
NSLock *
theLock
=
[
[
NSLock
alloc
]
init
]
;
TestObj *
obj
=
[
[
TestObj
alloc
]
init
]
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
static
void
(
^
TestMethod
)
(
int
)
;
TestMethod
=
^
(
int
value
)
{
[
theLock
lock
]
;
if
(
value
>
0
)
{
[
obj
method1
]
;
sleep
(
5
)
;
TestMethod
(
value
-
1
)
;
}
[
theLock
unlock
]
;
}
;
TestMethod
(
5
)
;
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
sleep
(
1
)
;
[
theLock
lock
]
;
[
obj
method2
]
;
[
theLock
unlock
]
;
}
)
;
|
以上的代码中,就是一种典型的死锁情况,因为在线程1中的递归block中,锁会被多次的lock,所以自己也被阻塞了,由于以上的代码非常的简短,所以很容易能识别死锁,但在较为复杂的代码中,就不那么容易发现了,那么如何在递归或循环中正确的使用锁呢?此处的theLock如果换用NSRecursiveLock对象,问题便得到解决了,NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
2.NSConditionLock条件锁
当我们在使用多线程的时候,有时一把只会lock和unlock的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//主线程中
NSConditionLock *
theLock
=
[
[
NSConditionLock
alloc
]
init
]
;
//线程1
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
for
(
int
i
=
0
;
i
<=
2
;
i
++
)
{
[
theLock
lock
]
;
NSLog
(
@
"thread1:%d"
,
i
)
;
sleep
(
2
)
;
[
theLock
unlockWithCondition
:
i
]
;
}
}
)
;
//线程2
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
[
theLock
lockWhenCondition
:
2
]
;
NSLog
(
@
"thread2"
)
;
[
theLock
unlock
]
;
}
)
;
|
在线程1中的加锁使用了lock,所以是不需要条件的,所以顺利的就锁住了,但在unlock的使用了一个整型的条件,它可以开启其它线程中正在等待这把钥匙的临界地,而线程2则需要一把被标识为2的钥匙,所以当线程1循环到最后一次的时候,才最终打开了线程2中的阻塞。但即便如此,NSConditionLock也跟其它的锁一样,是需要lock与unlock对应的,只是lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,当然这是与你的需求相关的。
3.NSDistributedLock分布式锁
以上所有的锁都是在解决多线程之间的冲突,但如果遇上多个进程或多个程序之间需要构建互斥的情景该怎么办呢?这个时候我们就需要使用到NSDistributedLock了,从它的类名就知道这是一个分布式的Lock,NSDistributedLock的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但NSDistributedLock并非继承于NSLock,它没有lock方法,它只实现了tryLock,unlock,breakLock,所以如果需要lock的话,你就必须自己实现一个tryLock的轮询,下面通过代码简单的演示一下吧:
程序A:
1
2
3
4
5
6
7
8
|
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
lock
=
[
[
NSDistributedLock
alloc
]
initWithPath
:
@
"/Users/mac/Desktop/earning__"
]
;
[
lock
breakLock
]
;
[
lock
tryLock
]
;
sleep
(
10
)
;
[
lock
unlock
]
;
NSLog
(
@
"appA: OK"
)
;
}
)
;
|
程序B:
1
2
3
4
5
6
7
8
9
10
|
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
lock
=
[
[
NSDistributedLock
alloc
]
initWithPath
:
@
"/Users/mac/Desktop/earning__"
]
;
while
(
!
[
lock
tryLock
]
)
{
NSLog
(
@
"appB: waiting"
)
;
sleep
(
1
)
;
}
[
lock
unlock
]
;
NSLog
(
@
"appB: OK"
)
;
}
)
;
|
先运行程序A,然后立即运行程序B,根据打印你可以清楚的发现,当程序A刚运行的时候,程序B一直处于等待中,当大概10秒过后,程序B便打印出了appB:OK的输出,以上便实现了两上不同程序之间的互斥。/Users/mac/Desktop/earning__是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在tryLock返回YES时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。