1

在我的应用程序中,我在每个芯片的多个管道上的多个芯片上执行大量异步 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 中一样。SO question 10094361 参考了一些分析,Apple Thread Programming Guide 指出“虽然锁是同步两个线程的有效方法,但获取锁是一项相对昂贵的操作,即使在无争议的情况下也是如此。” 我怎样才能以更有效的方式做到这一点?

  • 我从 Allocations 工具中知道,大量内存用于在我的循环中创建 NSDate 对象以检查事件匹配。我不能对匹配进行开放式检查,因为我的匹配标准可能永远不会得到满足。有什么更好的方法呢?

任何输入将不胜感激。有人可能会说使用 NSOperation/NSOperationQueue 或 GCD,但相信我,这个示例是简单操作之一。还有一些涉及多个读/写对,一些写/多个读等。

4

2 回答 2

1

您询问:

... Apple 线程编程指南指出“虽然锁是同步两个线程的有效方法,但获取锁是一项相对昂贵的操作,即使在无争议的情况下也是如此。” 我怎样才能以更有效的方式做到这一点?

这不是代码片段效率低下的根源,但如果您想知道替代方案,重构代码以使用队列有时会有所帮助。请参阅并发编程指南中的消除基于锁的代码,其中说:

用队列替换基于锁的代码消除了与锁相关的许多惩罚,还简化了剩余代码。您可以创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列不会施加与锁相同的惩罚。例如,排队任务不需要进入内核来获取互斥锁。

还有一个特别巧妙的 GCD 解决方案,用于使用读写器模式控制访问(使用并发队列、dispatch_sync读取、dispatch_barrier_async写入;这允许并发读取,但相对于异步完成的写入将它们序列化)。坦率地说,这种模式在这里可能并不适用,但在以下视频中进行了讨论:WWDC 2011 - Mastering GCDWWDC 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更有效的进程间通信(例如调度信号量)替换这些循环。

于 2013-08-10T02:22:46.267 回答
0

我知道 BOOL 的 set/get 函数中的 NSLock 访问很昂贵。

你怎么知道?请记住,您正在读取和写入 IO 设备。相比之下,锁将是微不足道的(除非您的 IO 通道与内存总线具有相似的速度)。

我知道带有超时检查的手工“睡眠”可能很昂贵

我没有看到任何睡眠,只有几个紧密的循环。它们在运行时肯定会占用一个 CPU 内核的 100%。

很难说如何改进这一点,你并没有真正展示足够的代码。您可以使用 NSOperations 来执行此操作,例如,您将有一个写入操作和一个针对每个所需读取的操作,并且您将使用依赖项来确保它们以正确的顺序执行,但是,假设读取和写入设备是通过读取和写入文件句柄来完成,我将使用运行循环和 NSFileHandles。

于 2013-08-08T20:46:34.743 回答