【sylar-webserver】3 线程模块

26 阅读7分钟

补充

Thread

对 pthread 库进行简单封装

pthread_create(&m_thread, nullptr, &Thraed::run, this);

// 主线程等待子线程结束,回收资源
pthread_join(m_thread);

// 主线程和子线程分离,子线程结束后,资源自动回收。
pthread_detach(m_thead);

使用 static thread_local 定义线程全局变量

static thread_local Thread *t_thread = nullptr;
static thread_local std::string t_thread_name = "UNKNOW";

获取 系统分配的 线程 id。PID

syscall(SYS_gettid);

设置 线程 名字

// 线程名称不能超过16字节,包括结尾的'\0'字符
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());

Thread类

class Thread : Noncopyable {
public:
    typedef std::shared_ptr<Thread> ptr;
    Thread(std::function<void()> cb, const std::string& name);

    ~Thread();

    pid_t getId() const {return m_id;}

    const std::string& getName() const {return m_name;}

    void join();

    // 下面四个静态方法,是为了方便访问 在 thread.cc 里定义的 t_thread, t_thread_name 的。
    // 所以操作对象都是 t_thread t_thread_name
    static Thread *GetThis();

    static const std::string &GetName();
    
    static void SetName(const std::string &name);

    static void* run(void* args);
    
private:
    // 线程id
    pid_t m_id= -1;
    // 线程
    pthread_t m_thread = 0;
    // 线程执行函数
    std::function<void()> m_cb;
    // 线程名
    std::string m_name;

    Semaphore m_semaphore; // 用于子线程创建之后通知夫线程
};

static run() 设置静态 a. pthread_create 只能给目标函数传一个值,传 this 最好 ⭐ b. 非静态 run,本身还有 this。使用设置为 static。

知识点

1 信号量 / 互斥量

#include <semaphore.h>

sem_t m_semphore;

/**
	初始化信号量:
	extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));  
	sem为指向信号量结构的一个指针;
	pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
	value给出了信号量的初始值。
*/

// 当前进程的所有线程共享;
sem_init(&m_semaphore, 0, count);

sem_post(&m_semaphore);

sem_wait(&m_semaphore);

sem_destroy(&m_semaphore);

模板类实现 RAII

局部锁的模板实现

template<class T>
struct ScopedLockImpl{
public:
	ScopedLockImpl(T& mutex) : m_mutex(mutex){
		m_mutex.lock();
		m_locked = true;
	}

	~ScopedLockImpl(){
		m_mutex.unlock();	
	}

	void lock(){
		if(!m_locked){
			m_mutex.lock();
			m_locked = true;
		}
	}

	void unlock(){
		if(m_locked){
			m_mutex.unlock();
			m_locked = false;
		}
	}

private:
	T& m_mutex;
	bool m_locked;		// 增加一个 bool 判断,避免重复lock,unlock
};

局部读锁模板实现

template<class T>
struct ReadScopedLockImpl{
public:
	ReadScopedLockImpl(T& mutex) : m_mutex(mutex){
		m_mutex.rdlock();
		m_locked = true;
	}
	
	~ReadScopedLockImpl(){
		unlock();
	}

	void lock(){
		if(!m_locked){
			m_mutex.rdlock();
			m_locked = true;
		}
	}
	
	void unlock(){
		if(m_locked){
			m_mutex.unlock();
			m_locked = false;
		}
	}

private:
	T& m_mutex;
	bool m_locked;
};

局部写锁模板实现

template<class T>
struct WriteScopedLockImpl{
public:
    WriteScopedLockImpl(T& mutex) : m_mutex(mutex) {
        m_mutex.wrlock();
        m_locked = true;
    }

    ~WriteScopedLockImpl(){
        unlock();
    }

    void lock(){
        if(!m_locked){
            m_mutex.wrlock();
            m_locked = true;
        }
    }

    void unlock(){
        if(m_locked){
            m_mutex.unlock();
            m_locked = false;
        }
    }

private:
    // mutex
    T& m_mutex;
    bool m_locked;
};

2 互斥锁 mutex

#include <thread>

pthread_mutex_init(&m_mutex, nullptr);

pthread_mutex_lock(&m_mutex);

pthread_mutex_unlock(&m_mutex);

pthread_mutex_destroy(&m_mutex);

实现互斥锁

class Mutex{
public:
    typedef ScopedLockImpl<Mutex> Lock;
    Mutex(){
        pthread_mutex_init(&m_mutex, nullptr);
    }

    ~Mutex(){
        pthread_mutex_destroy(&m_mutex);
    }

    void lock(){
        pthread_mutex_lock(&m_mutex);
    }
    
    void unlock(){
        pthread_mutex_unlock(&m_mutex);
    }

private:
    pthread_mutex_t m_mutex;
};

3 读写锁 rmutex

基本原则如下:   1.如果当前线程读数据,则允许其他线程执行读操作,但不允许写操作;   2.如果当前线程写数据,则其他线程的读、写操作均不允许。      因此,读写锁分为了读锁和写锁。具体如下所示:   1.如果某线程申请了读锁,其他线程可以再申请读锁,但不能申请写锁;   2.如果某线程申请了写锁,则其他线程不能申请读锁,也不能申请写锁。

#include <thread>

pthread_rwlock_t m_lock;

/**
 第1个参数rwlock是指向要初始化的读写锁的指针;
 第2个参数attr指向属性对象的指针,该属性定义要初始化的读写锁的特性,此参数设置为NULL时,则使用默认属性。
  函数执行成功是返回0,否则返回一个错误编号,以指明错误。

宏PTHREAD_RWLOCK_INITIALIZER初始化静态分配的读写锁。这相当于调用pthread_rwlock_init()动态初始化时指定attr参数为NULL。区别在于,它不执行错误检查。下面是使用此宏初始化读写锁代码:
(phtread_rwlock_initializer)
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
*/
pthread_rwlock_init(&m_lock, nullptr);
pthread_rwlock_rdlock(&m_lock);
pthread_rwlock_wrlock(&m_lock);
pthread_rwlock_unlock(&m_lock);
pthread_rwlock_destroy(&m_lock);

实现读写锁

// 读写互斥量
class RWMutex{
public:
    typedef ReadScopedLockImpl<RWMutex> ReadLock;
    typedef WriteScopedLockImpl<RWMutex> WriteLock;

    RWMutex(){
        pthread_rwlock_init(&m_lock, nullptr);
    }

    ~RWMutex(){
        pthread_rwlock_destroy(&m_lock);
    }

    void rdlock(){
        pthread_rwlock_rdlock(&m_lock);
    }

    void wrlock(){
        pthread_rwlock_wrlock(&m_lock);
    }

    void unlock(){
        pthread_rwlock_unlock(&m_lock);
    }

private:
    pthread_rwlock_t m_lock;
};

4. 自旋锁

在这里插入图片描述 优先用 CASLock:适用于轻量级同步且需跨平台的场景。 慎用 SpinkLock:仅在高并发、临界区极短的特定场景下使用。

4.1 pthread_spinlock_t

通过操作系统提供的自旋锁 API 实现,属于忙等待(Busy Waiting)锁。 依赖 POSIX 系统(Linux)

class SpinkLock{
public:
    typedef ScopedLockImpl<SpinkLock> Lock;
    SpinkLock(){
        pthread_spin_init(&m_mutex, 0);
    }

    ~SpinkLock(){
        pthread_spin_destroy(&m_mutex);
    }

    void lock(){
        pthread_spin_lock(&m_mutex);
    }
    
    void unlock(){
        pthread_spin_unlock(&m_mutex);
    }

private:
    pthread_spinlock_t m_mutex;
};

4.2 atomic_flag

使用原子操作(Compare-And-Swap,CAS)实现,通过循环尝试修改标志位来获取锁。 C++ 11,跨平台

#inlcude <atomic>

/** 
	初始化
	std::atomic_flag m_mutex = ATOMIC_FLAG_INIT;
	std::atomic_flag m_mutex = {0};
	std::atomic_flag m_mutex{};
	C++11 之前,使用默认构造函数,未定义状态
	C++20 之后,默认初始化false
*/
std::atomic_flag m_mutex;

/** 
	将存储在 *this 中的 bool 标志设置为 false(限于指定的 memory_order 约束)。
	m_mutex.clear(memory_order m = memory_order_seq_cst);	// 默认值
*/

原子bool类型,实现自旋锁

class CASLock{
public:
    typedef ScopedLockImpl<CASLock> Lock;
    CASLock(){
        m_mutex.clear(); 
    }

    ~CASLock(){
    }

    void lock(){
    	//std::memory_order_acquire  // 获取锁时保证可见性
        while(std::atomic_flag_test_and_set_explicit(&m_mutex, std::memory_order_acquire));
    }
    
    void unlock(){
    	//std::memory_order_release  // 释放锁时保证写入可见
        std::atomic_flag_clear_explicit(&m_mutex, std::memory_order_release);
    }

private:
    std::atomic_flag m_mutex;
};

5 pthread

// 导入 pthread库

pthread_t m_thread;

/**
	若创建成功,返回0;若出错,则返回错误编号。
	
	thread 是线程标识符,但这个参数不是由用户指定的,而是由 pthread_create 函数在创建时将新的线程的标识符放到这个变量中。
	attr 指定线程的属性,可以用 NULL 表示默认属性。
	start_routine 指定线程开始运行的函数。
	arg 是 start_routine 所需要的参数,是一个无类型指针。
*/
int pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

/**
	pthread_join 函数会让调用它的线程等待 threadid 线程运行结束之后再运行。	
	value_ptr 存放了其他线程的返回值。 nullptr

	一个可以被join的线程,仅仅可以被别的一个线程 join,如果同时有多个线程尝试 join 同一个线程时,最终结果是未知的。另外,线程不能 join 自己。
*/
int pthread_join(pthread_t threadid, void **value_ptr);

/**
	主线程与子线程分离,子线程结束后,资源自动回收。
*/
int pthread_detach(pthread_t threadid);

6 thread_local

项目里大量使用 static thread_local 变量保存数据。

thread_local 只对声明于命名空间作用域的对象、声明于块作用域的对象以及静态数据成员允许。它指示对象拥有线程存储期。它能与 static 或 extern 结合,以分别指定内部或外部链接(除了静态数据成员始终拥有外部链接),但附加的 static 不影响存储期。

修饰的变量种类,可划分为两大类:一种和class类有关,一种和class类无关。和class类有关在线程结束时,会调用class的析构函数。 如果在类的成员函数内定义了 thread_local 变量,则对于同一个线程内的该类的多个对象都会共享一个变量实例,并且只会在第一次执行这个成员函数时初始化这个变量实例,这一点是跟类的静态成员变量是相似的。 当使用thread_local描述类成员变量时,必须是static的。 传统的static类成员变量是所有对象(不区分线程)共享,而static thread_local修饰的时候会变成在线程内共享,线程间不是共享的。 (所以当需要统计某个子线程里的某个类,实例化的次数 可以使用 static thread_local类内成员变量计数)

thread_local是不需要锁