我已经阅读了相当多的线程安全性,并且已经使用GCD将数学密码保留在主线程上一段时间了(我在NSOperation之前了解了它,它似乎仍然存在是更容易的选择)。但是,我想知道我是否可以改进目前使用锁定的部分代码。
我有一个Objective-C ++类,它是c ++向量的包装器。 (原因:原始浮点数在不事先知道限制的情况下不断添加,容器必须是连续的,使用向量与NSMutableData的原因是"只是因为"它是我所确定的,并且NSMutableData在调整自身大小时仍会受到相同的"过期"指针的影响。)
该类具有实例方法,用于添加处理并添加到向量的数据点(vector.push_back)。添加新数据后,我需要对其进行分析(通过不同的对象)。该处理在后台线程上发生,它使用指针直接指向向量。目前,包装器有一个getter方法,它首先锁定实例(它暂停写入的本地串行队列),然后返回指针。对于那些不了解的人来说,这样做是因为当向量用完空间时,push_back会导致向量在内存中移动以为新条目腾出空间 - 使传递的指针无效。完成后,数学繁重的代码将在包装器上调用unlock,并且包装器将恢复排队的写入完成。
我没有看到一种方法来传递指针 - 在一段未知的时间内 - 没有使用某种类型的锁或制作本地副本 - 这将是非常昂贵的。
基本上:有没有更好的方法将原始指针传递给向量(或NSMutableData,对于那些被向量挂起的那些),在使用指针时,对向量的任何添加都会排队并且然后当指针的消费者完成时,自动"解锁"向量并处理写队列
当前实施
类:
DataArray
:C ++向量的包装器DataProcessor
:获取最原始的数据并清理它,然后再将其发送到' DataArray' DataAnalyzer
:采用' DataArray'指针并对数组进行分析Worker
:拥有并初始化所有3个,它还协调动作(它还做其他的东西,超出了这里的范围)。它也是处理器和分析器的代表会发生什么:
Worker
正在侦听来自处理外部设备的其他类的新数据NSNotification
时,它会通过DataProcessor
-(void)checkNewData:(NSArray*)data
DataProcessor
,在后台线程中工作会清理数据(并保留部分数据),然后将DataArray
告诉-(void)addRawData:(float)data
(如下所示)DataArray
然后存储该数据DataProcessor
时,它会告诉Worker
Worker
处理完毕后,它会通过DataAnalyzer
-(void)analyzeAvailableData
开始处理新数据
DataAnalyzer
做了一些准备工作,包括通过DataArray
向- (float*)dataPointer
询问指针(如下所示)DataAnalyzer
对全局线程执行dispatch_async
并开始繁重的工作。它需要整个时间访问dataPointer。dispatch_async
,告诉DataArray
解锁数组。DataArray
也可以被其他对象访问以用于只读目的,但其他那些读取速度非常快。 来自DataArray
-(void)addRawData:(float)data {
//quick sanity check
dispatch_async(addDataQueue, ^{
rawVector.push_back(data);
});
}
- (float*)dataPointer {
[self lock];
return &rawVector[0];
}
- (void)lock {
if (!locked) {
locked = YES;
dispatch_suspend(addDataQueue);
}
}
- (void)unlock {
if (locked) {
dispatch_resume(addDataQueue);
locked = NO;
}
}
来自DataAnalyzer
-(void)analyzeAvailableData {
//do some prep work
const float *rawArray = [self.dataArray dataPointer];
dispatch_async(global_queue, ^{
//lots of analysis
//done
dispatch_async(main_queue, ^{
//tell `Worker` analysis is done
[self.dataArray unlock];
};
};
}
答案 0 :(得分:0)
这实际上取决于你正在做什么,大部分时间我都是使用dispatch_async()
或dispatch_sync()
回到主队列(或专门指定的队列)。
如果可以的话,异步显然会更好。
这将取决于您的具体用例,但有时dispatch_async / dispatch_sync比创建锁定快几个数量级。
宏大中央调度(和NSOperationQueue)的重点是消除传统线程编程中发现的许多瓶颈,包括锁。
关于你对NSOperation
更难使用的评论......这是真的,我也不经常使用它。但它确实具有有用的功能,例如,如果您需要能够在执行中途终止任务,或者甚至在它开始执行之前终止任务,NSOperation
是可行的方法。
答案 1 :(得分:0)
即使没有锁定,也有一种简单的方法可以获得所需的东西。我们的想法是,您拥有共享的,不可变的数据,或者您拥有独有的可变数据。您不需要锁定共享的不可变数据的原因是它只是只读的,因此在写入期间不会出现竞争条件。
您需要做的就是根据您当前的需要在两者之间切换:
这两项操作都是按需执行的。假设使用C ++,您可以使用std::shared_ptr<vector const>
作为共享的不可变数据,使用std::unique_ptr<vector>
作为独占访问的可变数据。对于较旧的C ++标准,可以使用boost::shared_ptr<..>
和std::auto_ptr<..>
。请注意在共享版本中使用const
并且您可以轻松地从独占转换为共享版本,但是反转是不可能的,为了从不可变向量获取可变,您必须复制。
请注意,我假设无法复制样本数据,并且不会破坏算法的复杂性。如果这不起作用,那么在后台操作正在进行时使用废料空间的方法可能是最好的方法。您可以使用类似于智能指针的专用结构自动执行一些操作。
答案 2 :(得分:0)
如果您有一个共享资源(您的向量),它将通过来自不同任务的读取和写入来同时访问,您可以将专用调度队列与此资源相关联,这些任务将独占运行。
也就是说,对此资源(读取或写入)的每次访问都将仅在该调度队列上执行。我们将此队列命名为“sync_queue”。
此“sync_queue”可以是串行队列或并发队列。
如果它是串行队列,那么很明显所有访问都是线程安全的。
如果它是并发队列,您可以允许读取访问同时发生,即您只需调用dispatch_async(sync_queue, block)
:
dispatch_async(sync_queue, ^{
if (_shared_value == 0) {
dispatch_async(otherQueue, block);
}
});
如果该读取访问权限将“移动”到在不同执行上下文上执行的调用站点,则应使用同步版本:
__block int x;
dispatch_sync(sync_queue, ^{
x = _shared_value;
});
return x;
任何写访问都需要独占访问资源。拥有并发队列,您可以通过使用障碍:
来实现此目的dispatch_barrier_async(sync_queue, ^{
_shared_value = 0;
dispatch_async(mainQueue, ^{
NSLog(@"value %d", _shared_value);
});
});