实现从原始包装器一次一对地访问指针

时间:2014-05-02 19:51:44

标签: objective-c multithreading thread-safety grand-central-dispatch stdvector

我已经阅读了相当多的线程安全性,并且已经使用GCD将数学密码保留在主线程上一段时间了(我在NSOperation之前了解了它,它似乎仍然存在是更容易的选择)。但是,我想知道我是否可以改进目前使用锁定的部分代码。

我有一个Objective-C ++类,它是c ++向量的包装器。 (原因:原始浮点数在不事先知道限制的情况下不断添加,容器必须是连续的,使用向量与NSMutableData的原因是"只是因为"它是我所确定的,并且NSMutableData在调整自身大小时仍会受到相同的"过期"指针的影响。)

该类具有实例方法,用于添加处理并添加到向量的数据点(vector.push_back)。添加新数据后,我需要对其进行分析(通过不同的对象)。该处理在后台线程上发生,它使用指针直接指向向量。目前,包装器有一个getter方法,它首先锁定实例(它暂停写入的本地串行队列),然后返回指针。对于那些不了解的人来说,这样做是因为当向量用完空间时,push_back会导致向量在内存中移动以为新条目腾出空间 - 使传递的指针无效。完成后,数学繁重的代码将在包装器上调用unlock,并且包装器将恢复排队的写入完成。

我没有看到一种方法来传递指针 - 在一段未知的时间内 - 没有使用某种类型的锁或制作本地副本 - 这将是非常昂贵的。

基本上:有没有更好的方法将原始指针传递给向量(或NSMutableData,对于那些被向量挂起的那些),在使用指针时,对向量的任何添加都会排队并且然后当指针的消费者完成时,自动"解锁"向量并处理写队列

当前实施

类:

  • DataArray:C ++向量的包装器
  • DataProcessor:获取最原始的数据并清理它,然后再将其发送到' DataArray'
  • DataAnalyzer:采用' DataArray'指针并对数组进行分析
  • Worker:拥有并初始化所有3个,它还协调动作(它还做其他的东西,超出了这里的范围)。它也是处理器和分析器的代表

会发生什么:

  1. Worker正在侦听来自处理外部设备的其他类的新数据
  2. 当收到带有数据包的NSNotification时,它会通过DataProcessor
  3. 将其传递到-(void)checkNewData:(NSArray*)data
  4. DataProcessor,在后台线程中工作会清理数据(并保留部分数据),然后将DataArray告诉-(void)addRawData:(float)data(如下所示)
  5. DataArray然后存储该数据
  6. 当使用当前块完成DataProcessor时,它会告诉Worker
  7. 当通知Worker处理完毕后,它会通过DataAnalyzer
  8. 告知-(void)analyzeAvailableData开始处理新数据
  9. DataAnalyzer做了一些准备工作,包括通过DataArray- (float*)dataPointer询问指针(如下所示)
  10. DataAnalyzer对全局线程执行dispatch_async并开始繁重的工作。它需要整个时间访问dataPointer。
  11. 完成后,它会向主线程执行dispatch_async,告诉DataArray解锁数组。
  12. DataArray也可以被其他对象访问以用于只读目的,但其他那些读取速度非常快。
  13. 来自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];
            };
        };
    }
    

3 个答案:

答案 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);
    });
});