【资料图】
std::unique_lock
unique_lock比lock_guard更加灵活,它拥有成员函数lock、unlock、try_lock,但占用空间更大,速度也稍慢。“死锁”一节中最后的代码也可以像下面这样写:
std::unique_lock实例没有与自身相关的互斥量,即一个unique_lock对象并不拥有一个特定的互斥量,它可以通过移动操作来获得对互斥量的所有权,并在不同的unique_lock实例之间传递。在某些情况下,这种转移是自动发生的,例如当函数返回一个unique_lock实例时。但在其他情况下,需要显式地调用std::move来执行移动操作。
锁的粒度
锁的粒度用来描述通过一个锁保护着的数据量大小。一个细粒度锁(a fine-grained lock)能够保护较少的数据量,一个粗粒度锁(a coarse-grained lock)能够保护较多的数据量。选择粒度对于锁来说很重要,为了保护对应的数据,确保锁有能力保护这些数据也很重要。
如果很多线程正在等待同一个资源,当有线程持有锁的时间过长,就会增加等待的时间。std::unique_lock的灵活性在此时就可以发挥作用。当线程暂时不需要访问共享数据时,可以调用unlock;当再次需要访问共享数据时,可以再调用lock。
Case:
不仅需要让锁能锁住合适粒度的数据,还要控制锁的持有时间,以及判断哪些操作在执行的同时能够拥有锁。一般情况下,执行必要的操作时,尽可能将持有锁的时间缩减到最小。
Case:
在上述代码中比较两个Instance对象时,由于int的拷贝很容易,所以选择先将i和j的content分别赋值给vi和vj后,再对vi和vj进行比较。这样做的好处是减少了锁持有的时间(仅在执行getContent函数时持有锁),并且一个锁只持有一次,消除了死锁的可能性。但是这样做的后果是——这两个值在读取之后,可能会被任意的方式所修改,比较的结果可能为true,但实际上这两个值相等的情况可能仅仅发生在某个瞬间。假设刚刚完成对vi的赋值后,此时该线程既不持有i的锁,也不持有j的锁,如果有另一个线程在此刻获取了i和j的锁,执行了交换i和j的content的操作,那么之后vj也将被赋予和vi相同的值,比较也就失去了意义。
Warning:当你持有锁的时间没有达到整个操作时间,就会让自己处于条件竞争的状态。
在这个例子中,需要寻找一个合适的机制,去替换std::mutex。
关键词: