webServer设计(一)—— 线程同步类

1 不可复制的类

互斥锁pthread_mutex_t本就不建议通过复制的方式来初始化,而应该在所属线程使用专门的初始化函数进行管理,封装成类之后也应该保证其不被拷贝。

#ifndef BASE_UNCOPYABLE_H
#define BASE_UNCOPYABLE_H

class uncopyable{
public:
    //c++11的新机制,确保拷贝构造函数和拷贝复制运算符无效。
    uncopyable(const uncopyable&)=delete;
    void operator=(const uncopyable&)=delete;
protected:
    uncpoyable() {}
    ~Uncopyable() {}
}
#endif  //BASE_UNCOPYABLE_H

2 互斥锁

​ 使用RAII机制来管理互斥锁mutex,通过作用域来管理临界区,实现锁的自动释放。

//Mutex.h
#ifndef BASE_MUTEX_H
#define BASE_MUTEX_H

#include <pthread.h>
#include <assert.h>
#include <uncopyable.h>
//互斥锁,无法被复制。
class MutexLock : uncopyable {    
public:
    MutexLock()
    {
        pthread_mutex_init(&mutex_,NULL);
    }
    ~MutexLock()
    {
        //pthread_mutex_lock(&mutex_);
        pthread_mutex_destroy(&mutex_);
    }
    void lock() { pthread_mutex_lock(&mutex_); }
    void unlock() { pthread_mutex_unlock(&mutex_); }
    pthread_mutex_t *getMutex(){    //仅供Condition使用。
        return &mutex_;
    }

private:
    pthread_mutex_t mutex_;

private:
    friend class Condition;
};
//临界锁,使用后的区域即为临界区。
class MutexLockGuard : uncopyable {
public:
    explicit MutexLockGuard(MutexLock& mutex):mutex_(mutex) { mutex_.lock(); }    //临界区加锁
    ~MutexLockGuard(){ mutex_.unlock(); }    //临界区解锁

private:
    MutexLock &mutex_;
};

#endif //BASE_MUTEX_H

3 条件量

​ 条件量主要用于线程间的同步,一个线程阻塞于wait函数,等待另外一个线程执行并notify()/notifyAll()。

  1. 用于主线程等待单个子线程完成任务
  2. 用于多个子线程等待主线程的“起跑”命令
//Condition.h
#ifndef BASE_CONDITION_H
#define BASE_CONDITION_H

#include <pthread.h>
#include <time.h>
#include <cstdint>
#include "Mutex.h"
#include "uncopyable.h"

class Condition : uncopyable {
public:
    explicit Condition(MutexLock &mutex):mutex_(mutex) {
        pthread_cond_init(&cond_,NULL);
    }
    ~Condition() { pthread_cond_destroy(&cond_); }
    void wait() { pthread_cond_wait(&cond_,mutex_.getMutex()); }
    void notify() { pthread_cond_signal(&cond_); }
    void notifyAll() { pthread_cond_broadcast(&cond_); }
    bool waitForSeconds(int seconds) {
        struct timespec abstime;
        clock_gettime(CLOCK_REALTIME, &abstime);
        abstime.tv_sec += static_cast<time_t>(seconds);
    return ETIMEDOUT == pthread_cond_timedwait(&cond, mutex.get(), &abstime);
    }
private:
    MutexLock &mutex_;
    pthread_cond_t cond_;
};
#endif //BASE_CONDTION_H

4 倒计时同步

CountDownLatch与Condition的区别主要在于CountDownLatch有一个计数功能,调用cout次countDown之后wait才会停止阻塞。

  1. 用于主线程等待多个子线程完成初始化;
  2. 用于多个子线程等待主线程发出“起跑”命令。
//CountDownLatch.h
#ifndef BASE_COUNTDOWNLATCH_H
#define BASE_COUNTDOWNLATCH_H

#include "Condition.h"
#include "MutexLock.h"
#include "uncopyable.h"

class CountDownLatch : uncopyable {
public:
    explicit CountDownLatch(int count);
    void wait();
    void countDown();
private:
    mutable MutexLock mutex_;
    Condition condition_;
    int count_;
};
#endif
//CountDownLatch.cpp
#include "CountDownLatch.h"

CountDownLatch::countDownLatch(int count)
    :mutex_(),condition(mutex_),count_(count) {}

void CountDownLatch::wait() {
    MutexLockGuard lock(mutex_);
    while(count_>0) condition_.wait();
}

void CountDownLatch::countDown() {
    MutexLockGuard lock(mutex_);
    --count_;
    if(count_==0) condition_.notifyAll();
}

5 总结

  1. 互斥锁的主要作用是保证对临界区访问的顺序执行。比如进程间共享的条件量或者计数值。
  2. 条件量主要是用于线程间的同步,一个(或多个)线程阻塞直到另一线程达成条件。
  3. 倒计时类与条件量相比多了一个计数功能,能够限定代码的执行次数或者保证多个线程的同步。
  4. 在webserver的设计中,互斥量主要与条件量结合使用,一个是在线程池中,主线程通过条件量等待子线程函数的执行。