补充
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是不需要锁