在我的应用程序中,我在每个芯片的多个管道上的多个芯片上执行大量异步I / O.
某些操作由多个操作组成。例如,要从其中一个芯片读取序列号,我必须执行两次写入和两次读取:写入命令以检查序列号缓冲区大小,读取结果。写命令读取序列号值,读取结果。
以下是该操作的简化代码:
- (BOOL)readSerialNumber:(NSMutableString*)serialNumber
{
if(nil == serialNumber)
return FALSE; // Nowhere to store.
if(![self sendCommand:GetSerialNumberSize])
return FALSE;
// Set up some matching event data (not shown), then check it....
BOOL matched= FALSE;
BOOL timeUp= FALSE;
[self setEventWasMatched:FALSE];
NSDate* timeUpDate= [[NSDate alloc] initWithTimeIntervalSinceNow:2.0];
while((!timeUp) && !matched)
{
matched= [self eventWasMatched]; // Match state is set from receive code in another thread.
timeUp= (NSOrderedDescending != [timeUpDate compare:[NSDate date]]);
}
[timeUpDate release];
NSUInteger serialNumberSize= matchedEvent.serialNumberSize;
if(0 == serialNumberSize)
return FALSE;
if(![self sendCommand:GetSerialNumber ofSize:serialNumberSize])
return FALSE;
// Set up some matching event data (not shown), then check it....
matched= FALSE;
timeUp= FALSE;
[self setEventWasMatched:FALSE];
timeUpDate= [[NSDate alloc] initWithTimeIntervalSinceNow:2.0];
while((!timeUp) && !matched)
{
matched= [self eventWasMatched]; // Match state is set from receive code in another thread.
timeUp= (NSOrderedDescending != [timeUpDate compare:[NSDate date]]);
}
[timeUpDate release];
[serialNumber.setString:matchedEvent.serialNumber];
return (0 != [serialNumber length]);
}
- (void)setEventWasMatched:(BOOL)matched
{
[lockMatch lock];
eventMatched= matched;
[lockMatch unlock];
}
- (void) eventWasMatched
{
BOOL wasMatched= FALSE;
[lockMatch lock];
wasMatched= eventMatched;
[lockMatch unlock];
return wasMatched;
}
此代码示例可能无法编译或工作,但它代表了我正在使用的代码。
这里有几件我有疑问的事情:
我一直认为BOOL的set / get函数中的NSLock访问很昂贵,就像在setEventWasMatched和eventWasMatched中一样。所以问题10094361引用了一些分析,Apple Thread Programming Guide声明“尽管锁定是同步两个线程的有效方法,但获取锁定是一项相对昂贵的操作,即使在无争议的情况下也是如此。”我怎样才能以更有效的方式做到这一点?
我从Allocations工具中了解到,在我的循环中使用了大量内存来创建NSDate对象来检查事件匹配。我不能有开放式的匹配检查,因为我的匹配标准可能永远不会得到满足。有什么更好的方法呢?
任何输入都将不胜感激。有些人可能会说使用NSOperation / NSOperationQueue或GCD,但请相信我,这个例子是一个简单的操作。还有其他涉及多个读/写对,一些写/多读等等。
答案 0 :(得分:2)
你问:
... Apple Thread Programming Guide声明“虽然锁是同步两个线程的有效方法,但获取锁是一项相对昂贵的操作,即使在无争议的情况下也是如此。”我怎样才能以更有效的方式做到这一点?
这不是您的代码段效率低下的原因,但如果您对备选方案感到疑惑,那么重构代码以使用队列有时会有所帮助。请参阅并发编程指南中的Eliminating Lock Based Code,其中包含:
使用队列替换基于锁的代码可以消除许多与锁相关的惩罚,并简化剩余的代码。您可以改为创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列不会像锁一样处罚。例如,排队任务不需要捕获内核来获取互斥锁。
还有一个特别巧妙的GCD解决方案,用于使用读写器模式控制访问(使用并发队列,dispatch_sync
读取,dispatch_barrier_async
写入;这允许并发读取,但序列化它们相对于写入,异步完成)。坦率地说,这是一种可能不适用的模式,但在以下视频中对此进行了讨论:WWDC 2011 - Mastering GCD或WWDC 2012 - Asynchronous Design Patterns。
如果你正在寻找效率,那么锁不是这里的主要罪魁祸首。这是while
循环。通过使用调度信号量或类似的东西来解决这个问题,例如,在发送请求之后,使用:
if(![self sendCommand:GetSerialNumberSize])
return FALSE;
self.semaphore = dispatch_semaphore_create(0);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_semaphore_wait(self.semaphore, timeout);
if (!self.eventWasMatched) {
// we must have timed out
}
// carry on ...
和ReadPipeAsync
例程可以:
obj.eventWasMatched = YES; // update all the other relevant properties, too
dispatch_semaphore_signal(obj.semaphore); // now inform other process that we had a match
这样的事情可能会更有效率。
你继续问:
我从Allocations工具中了解到,在我的循环中使用了大量内存来创建NSDate对象来检查事件匹配。我不能有开放式的匹配检查,因为我的匹配标准可能永远不会得到满足。有什么更好的方法呢?
是的,您的代码会创建大量autorelease
个对象,在池耗尽之前不会释放这些对象。显然,你应该完全删除这些while
循环,但是,仅仅为了争论,如果你想在这里修复内存问题,你可以将它包装在@autoreleasepool
中,这样你就可以了以更高的频率排空游泳池:
NSDate* timeUpDate= [[NSDate alloc] initWithTimeIntervalSinceNow:2.0];
while((!timeUp) && !matched)
{
@autoreleasepool {
matched = [self eventWasMatched]; // Match state is set from receive code in another thread.
timeUp = (NSOrderedDescending != [timeUpDate compare:[NSDate date]]);
}
}
[timeUpDate release];
或者,更好的是,根本不要使用自动释放对象:
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
while((!timeUp) && !matched)
{
matched = [self eventWasMatched]; // Match state is set from receive code in another thread.
timeUp = ((CFAbsoluteTimeGetCurrent() - startTime) >= 2.0);
}
但是,正如我之前所说,这确实是一个没有实际意义的问题,因为您应该使用更有效的进程间通信替换这些while
循环,例如调度信号量。
答案 1 :(得分:0)
我知道BOOL的set / get函数中的NSLock访问费用很高。
你怎么知道的?请记住,您正在读写IO设备。相比之下,锁定将是微不足道的(除非您的IO通道与内存总线具有相似的速度)。
我知道用时间检查手工制作的“睡眠”可能是昂贵的
我没有看到任何睡眠,只有几个紧密的循环。他们肯定会在运行时占用100%的CPU核心。
很难说如何改进这一点,你没有真正展示足够的代码。你可以用NSOperations来做,例如你会有一个写操作和每个必需读操作,你会使用依赖关系来确保它们以正确的顺序执行,但是,假设读取和写入设备是通过读写文件句柄来完成的,我会使用一个运行循环和NSFileHandles。