Does iOS have any kind of very low level condition lock that does not include locking? I am looking for a way to signal an awaiting thread from within the Core Audio render thread, without the usage of locks. I was wondering if something low level as a Mach system call might exist. Right now I have a Core Audio thread that uses a non-blocking thread safe message queue to send messages to another thread. The other thread then pulls every 100ms to see if messages are available in the queue. But this is very rudimentary and the timing is awful. I could use condition locks, but that involves locking, and I would like to keep any kind of locking out of the rendering thread. What I am looking for is having the message queue thread wait until the Core Audio render thread signals it. Just like pthread conditions, but without locking and without immediate context switching? I would like the Core Audio thread to complete before the message queue thread is woken up. |
|||||||||
|
UpdatedA
Original answer:You can use a mach Whether or not you end up using my wrapper or rolling your own the code will look roughly like:
|
|||||||||||||||||||||
|
// 创建一个信号量,值为0 dispatch_semaphore_t sema = dispatch_semaphore_create(0); // 在一个操作结束后发信号,这会使得信号量+1 ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { dispatch_semaphore_signal(sema); }); // 一开始执行到这里信号量为0,线程被阻塞,直到上述操作完成使信号量+1,线程解除阻塞 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// 创建一个组 dispatch_group_t group = dispatch_group_create(); // 创建信号 信号量为10 dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); // 取得默认的全局并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(inti = 0; i < 100; i++) { // 由于信号量为10 队列里面最多会有10个人任务被执行, 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);
semaphore_wait
Function - Wait on the specified semaphore.
SYNOPSIS
kern_return_t semaphore_wait (semaphore_t semaphore);
PARAMETERS
- semaphore
- [in send right] The port naming the semaphore that the wait operation is being performed upon.
DESCRIPTION
The semaphore_wait function decrements the semaphore count. If the semaphore count is negative after decrementing, the calling thread blocks. Device driver interrupt service routines (ISR) should never executesemaphore_wait, since waiting on a semaphore at the ISR level may, and often will, lead to a deadlock.
RETURN VALUES
- KERN_INVALID_ARGUMENT
- The specified semaphore is invalid.
- KERN_TERMINATED
- The specified semaphore has been destroyed.
- KERN_ABORTED
- The caller was blocked due to a negative count on the semaphore, and was awoken for a reason not related to the semaphore subsystem (e.g. thread_terminate).
- KERN_SUCCESS
- The semaphore wait operation was successful.
RELATED INFORMATION
Functions: semaphore_create, semaphore_destroy, semaphore_signal, semaphore_signal_all, device_get_status.
那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢?
今天一起来探讨一下Objective-C中几种不同方式实现的锁,在这之前我们先构建一个测试用的类,假想它是我们的一个共享资源,method1与method2是互斥的,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@implementationTestObj
-(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=[[TestObjalloc]init];
NSLock *lock=[[NSLockalloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
[locklock];
[objmethod1];
sleep(10);
[lockunlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
sleep(1);//以保证让线程2的代码后执行
[locklock];
[objmethod2];
[lockunlock];
});
|
看到打印的结果了吗,你会看到线程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=[[TestObjalloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
@synchronized(obj){
[objmethod1];
sleep(10);
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
sleep(1);
@synchronized(obj){
[objmethod2];
}
});
|
@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=[[TestObjalloc]init];
__blockpthread_mutex_tmutex;
pthread_mutex_init(&mutex,NULL);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
pthread_mutex_lock(&mutex);
[objmethod1];
sleep(5);
pthread_mutex_unlock(&mutex);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
sleep(1);
pthread_mutex_lock(&mutex);
[objmethod2];
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=[[TestObjalloc]init];
dispatch_semaphore_tsemaphore=dispatch_semaphore_create(1);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
[objmethod1];
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);
[objmethod2];
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=[[NSLockalloc]init];
TestObj
*obj=[[TestObjalloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
staticvoid(^TestMethod)(int);
TestMethod=^(intvalue)
{
[theLocklock];
if(value>0)
{
[objmethod1];
sleep(5);
TestMethod(value-1);
}
[theLockunlock];
};
TestMethod(5);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
sleep(1);
[theLocklock];
[objmethod2];
[theLockunlock];
});
|
以上的代码中,就是一种典型的死锁情况,因为在线程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=[[NSConditionLockalloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
for(inti=0;i<=2;i++)
{
[theLocklock];
NSLog(@"thread1:%d",i);
sleep(2);
[theLockunlockWithCondition:i];
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
[theLocklockWhenCondition:2];
NSLog(@"thread2");
[theLockunlock];
});
|
在线程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=[[NSDistributedLockalloc]initWithPath:@"/Users/mac/Desktop/earning__"];
[lockbreakLock];
[locktryLock];
sleep(10);
[lockunlock];
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=[[NSDistributedLockalloc]initWithPath:@"/Users/mac/Desktop/earning__"];
while(![locktryLock]){
NSLog(@"appB:
waiting");
sleep(1);
}
[lockunlock];
NSLog(@"appB:
OK");
});
|
先运行程序A,然后立即运行程序B,根据打印你可以清楚的发现,当程序A刚运行的时候,程序B一直处于等待中,当大概10秒过后,程序B便打印出了appB:OK的输出,以上便实现了两上不同程序之间的互斥。/Users/mac/Desktop/earning__是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在tryLock返回YES时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。