《Linux多线程服务端编程》读书笔记(二)

线程同步

线程同步的四项原则

  1. 最低限度地共享对象,一个对象尽量不暴露给其他线程,如果非要暴露,优先考虑immutable对象;实在不行才暴露可修改的对象,并用同步措施充分保护它。
  2. 使用高级的并发编程构件,如TaskQueue, Producer-Consumer Queue, CountDownLatch
  3. 不得已使用底层同步原语的时候,只使用非递归的互斥器和条件变量,慎用读写锁,不要用信号量
  4. 除了使用automic变量外,不自己编写lock-free代码,不要使用“内核级”同步原语

互斥器

使用原则:

  • 用RAII手法封装mutex的创建、销毁、加锁、解锁这四个操作
  • 只使用非递归的mutex
  • 不手工调用lock(), unlock()函数,一切交给栈上的Guard对象的构造和析构函数负责
  • 每次构造Guard对象需要关注调用栈上已经持有的锁,防止加锁顺序不同导致的死锁出现
只使用非递归的mutex

可重入mutex可以被同一个线程反复加锁,这就可能造成一些问题

MutexLock mutex;
std::vector<Foo> foos;

void post(const Foo& f) {
    MutexLockGuard lock(mutex);
    foos.push_back(f);
}

void traverse() {
    MutexLockGuard lock(mutex);
    for (std::vector<Foo>::const_iterator it = foos.begin();
        it != foos.end(); ++it) {
        it->doit();  // 这里可能有问题
    }
}

如果it->doit()间接的调用了post(),可能会出现下面两种情况

  1. mutex是不可重入的,那么就会造成死锁
  2. mutex是可重入的,由于push_back()可能会导致迭代器失效,所以程序可能偶尔会crash

条件变量

若干个线程需要等待某个表达式为真,即等待别的线程“唤醒”它。条件变量的学名叫管程(monitor)。Java object内置的wait(), notify(), notifyAll()是条件变量。

条件变量的正确使用方式,对于wait端:

  1. 必须与mutex一起使用,布尔表达式的读写需要受mutex的保护
  2. 在mutex已上锁的时候才能调用wait()
  3. 把判断布尔条件和wait()放到while循环中
std::mutex mutex;
std::condition_variable cv;
std::deque<int> queue;

int dequeue()
{
    std::unique_lock<std::mutex> lock(mutex);  // 这里不能使用lock_guard,因为lock_guard不支持手动解锁
    while(queue.empty()) {  // 必须用循环;必须在判断之后再wait()
        cv.wait(lock);  // 释放互斥量并等待条件变量,被唤醒时会重新尝试获取锁
    }
    assert(!queue.empty());
    int top = queue.front();
    queue.pop_front();
    return top;
}

对上面的代码有这样的思考

  1. 为什么必须使用循环?
    在使用void wait(unique_lock<mutex>& lck);的时候,获取到信号量就会无条件的执行下面的语句。如果一次notify()唤醒了多个线程,只有一个线程能正常执行,其他线程都会因为锁的争用阻塞到cv.wait();语句;当锁被释放时,这些线程会直接执行下面的代码,造成了错误。
    使用wait()具有两个参数的回调可以避免使用循环。
  2. 为什么要加锁?
    首先为了保证对条件变量的修改是串行的,其次为了保证对queue的修改是串行的

对于singal/broadcast端:

  1. 不一定要在持有锁的情况下调用signal
  2. singal之前一般要修改布尔表达式
  3. 修改布尔表达式通常要用mutex保护
  4. 注意区分singalbroadcastbroadcast通常表明状态的变化,所以需要唤醒所有等待该状态的线程;singal通常用于资源可用,所以唤醒一小部分线程来消耗资源。
void enqueue(int x) {
    std::lock_guard<std::mutex> lock(mutex);
    queue.push_back(x);
    cv.notify_one();
}

互斥器和条件变量构成了多线程编程的全部必备同步原语,用它们即可完成任何多线程同步任务,二者不能相互替代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值