1. 条件变量

在线程等待条件变量进入wait状态的时候,需要使用循环来判断条件是否成立。因为wait被唤醒,不一定是被signal了,还有其它可能。

  1. false sharing

每个cpu核心都有单独的L1 L2缓存,缓存的基本单位不是字节,而是cache line,一般为64字节,所以每次读取数据,都会读取相应的字节数。这样就可能会产生性能问题。假设有一个数组,为了降低锁损耗,每个线程使用数组里相应的元素。多个线程一起运行后,就会使得该数组读入每个cpu的cache里,均为S(share,多cpu cache相同数据缓存)状态。当某个线程需要修改自己的元素的时候,需要获得拥有权,修改当前cache line,于是cache line的状态会变为M(Modified),同时为了保证数据一致性,必须是得其它核心的缓存失效(I)。由于读每个元素的时候读了64字节,会把其余的数据读到缓存里,所以每次对自身线程cache line的失效都会影响到别的线程,导致不断的失效,重新读取,会很影响性能。

可以通过将变量填充为64字节解决。

  1. RCU Read-Copy Update

核心在于写入的时候,拷贝数据的副本进行修改。等待其余写入操作完成后,修改副本值原始数据中。难点在于修改副本至原始数据的时机。网上大概看了下,比较简单并且好理解的方法是使用了阶段计数器。

原理:

读的时候新增计数,读完后减少计数,当计数为0的时候,检查是否有副本,然后覆盖原始数据。这是最简单的想法,但也仅仅是想法,因为写入后的等待过程中,还会有源源不断的读过来,那么计数器为0是很难的。

为了解决这个问题,我们把原先的计数器分为2个,分别是old reader counter和new reader counter。在任意时刻(这儿理解有点不好,按我的理解应该在写入的时刻,这里有疑问),将原先的计数器停止计数,作为old reader counter,后续所有的计数都在new中。这样old只会降不会增,当old减少到0的时候,执行覆盖操作。

  1. Copy on write

COW也是一种能很大程度上提高并发性能的技术。

核心思想是假设要进行修改,则会拷贝一份完整的数据,修改好后写入原值。借助shared_ptr可以很好的完成这项工作。

下面看一下简单的代码片段:

void read() {
    std::shared_ptr<COWTaskList> tptr;
    // Read the pointer to make a copy and increase the reference count
    {
        qmu_.lock();
        tptr = queue_;
        qmu_.unlock();
    }

    // To traverse the list
    // tptr is point to the original list, and queue_ may be a new list if 
    // write is called
    printf("Task ");
    for (auto& task : *tptr) {
        printf("%d ", task.id);
    }
    printf("\r\n");
}

void write(const COWTask& task) {
    qmu_.lock();
    // Check reference
    if (!queue_.unique()) {
        // Read in progress, make a copy
        queue_.reset(new COWTaskList(*queue_));
        copy_times_++;
    } else {
        // No read, just push to queue
    }
    if (queue_->size() > 5) {
        queue_->pop_front();
    }
    queue_->push_back(task);
    qmu_.unlock();
}

成员定义:

std::atomic<int> copy_times_;
std::shared_ptr<COWTaskList> queue_;
std::mutex qmu_;

在read中,访问queue_来增加queue_的计数,然后通过queue_的拷贝来访问源list。而在write中,全程持锁,判断queue_是否独享,若是,则直接修改源list;若否,则进行拷贝,修改queue_指向为拷贝,然后进行修改。

假设read获取了原始指针的拷贝,则原始指针的计数会增加,假设在这个时候write,则write会进行复制,原始指针会指向新的副本,而read依旧有原始指针,只是引用计数只有1了,在函数退出后,原始数据将会被销毁;假设read还没有获取拷贝,则write直接修改原始数据。

共 0 条回复
暂时没有人回复哦,赶紧抢沙发
发表新回复

作者

sryan
today is a good day