目标C:等待线程完成的更好方法?

时间:2013-08-08 20:20:17

标签: objective-c multithreading thread-safety

在我的应用程序中,我在每个芯片的多个管道上的多个芯片上执行大量异步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,但请相信我,这个例子是一个简单的操作。还有其他涉及多个读/写对,一些写/多读等等。

2 个答案:

答案 0 :(得分:2)

你问:

  

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

这不是您的代码段效率低下的原因,但如果您对备选方案感到疑惑,那么重构代码以使用队列有时会有所帮助。请参阅并发编程指南中的Eliminating Lock Based Code,其中包含:

  

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

还有一个特别巧妙的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循环,例如调度信号量。

答案 1 :(得分:0)

  

我知道BOOL的set / get函数中的NSLock访问费用很高。

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

  

我知道用时间检查手工制作的“睡眠”可能是昂贵的

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

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