sylar-from-scratch----线程模块thread、mutex

152 阅读23分钟

该模块基于pthread类实现,主要是对pthread中关于多线程同步类信号量、互斥锁、读写锁、自旋锁和原子锁的初始化、上锁、解锁等接口的学习。各类大部分是使用类的构造函数来加锁,用析构函数来释放锁,避免了忘记解锁而导致的死锁问题。

线程同步mutex

1、信号量类Semaphore

  • 构造函数:Semaphore(uint32_t count = 0),默认值为0。使用semit_init函数初始化一个信号量,参数&m_semaphore表示要初始化的信号量对象,参数0表示信号量的线程共享级别(0表示在进程内的所有线程之间共享),参数count表示信号量的初始值。如果sem_init函数返回非零值,表示初始化失败,就会抛出一个std::logic_error异常,提示"sem_init error"。
Semaphore::Semaphore(uint32_t count) {
    if(sem_init(&m_semaphore, 0, count)) {
        throw std::logic_error("sem_init error");
    }
}
  • 析构函数:当没有线程在使用该信号量时,使用sem_destroy()函数释放信号量占用的一切资源。
Semaphore::~Semaphore() {
    sem_destroy(&m_semaphore);
}
  • 获取信号量函数wait:使用sem_wait()函数为信号量实现加1操作,对于小于0的信号量,当前线程会被阻塞,一直等到其大于0时再进行减1操作,表示锁定该信号量,此时线程锁定了信号量,可以对其进行相应操作。
void Semaphore::wait() {
    if(sem_wait(&m_semaphore)) {
        throw std::logic_error("sem_wait error");
    }
}
  • 释放信号量函数notify:使用sem_post()函数为信号量实现加1操作,为信号量解锁,释放资源,若有其它进程在等待该信号量,则其中一个将会被唤醒并执行。
void Semaphore::notify() {
    if(sem_post(&m_semaphore)) {
        throw std::logic_error("sem_post error");
    }
}

局部锁模板

template<class T>
struct ScopedLockImpl {
public:
    /**
     * @brief 构造函数
     * @param[in] mutex Mutex
     */
    ScopedLockImpl(T& mutex)
        :m_mutex(mutex) {
        m_mutex.lock();
        m_locked = true;//构造局部锁初始为上锁的状态
    }

    /**
     * @brief 析构函数,自动释放锁
     */
    ~ScopedLockImpl() {
        unlock();
    }

    /**
     * @brief 加锁
     */
    void lock() {
        if(!m_locked) {
            m_mutex.lock();
            m_locked = true;
        }
    }

    /**
     * @brief 解锁
     */
    void unlock() {
        if(m_locked) {
            m_mutex.unlock();
            m_locked = false;
        }
    }
private:
    /// mutex
    T& m_mutex;
    /// 是否已上锁
    bool m_locked;
};

2、互斥锁类Mutex

  • 构造函数:pthread_mutex_init()初始化互斥锁,互斥锁属性初始化为NULL,即使用default缺省属性PTHREAD_MUTEX_TIMED_NP,其为普通锁(当一个线程加锁时,其余请求锁的线程形成一个等待队列,在解锁后按照优先级获得锁,保障了资源分配的公平性);
  • 析构函数:pthread_mutex_destroy()销毁互斥锁;
  • 加锁:pthread_mutex_lock()对线程进行加锁,会使后续线程进入阻塞状态;
  • 解锁:pthread_mutex_unlock()对线程解锁,队列中的线程可以竞争该锁保护的临界资源;

3、读写互斥量类RWMutex

  • 构造函数:pthread_rwlock_init()读写锁初始化RWMutex,属性为NULL,为缺省时的默认属性PTHREAD_RWLOCK_INITIALIZER;
  • 析构函数:pthread_rwlock_destroy()销毁读写锁;
  • 上读锁:pthread_rwlock_rdlock(&m_lock),若没有写入器持有锁m_lock且没有任何写入器基于m_lock阻塞,则调用线程会获取读锁。因为读写锁允许多个读锁访问,但不允许读锁和写锁同时访问或多个写锁访问。
  • 上写锁:pthread_rwlock_wrlock(&m_lock),若没有其它读取线程或写入器持有读写锁m_lock,则调用线程获取写锁。
  • 解锁:pthread_rwlock_unlock(&m_lock),用来释放在m_lock所引用的读写锁对象所持有的锁,只有当最后一个读锁或写锁解锁后,调用线程不再是m_lock的属主,此时m_lock属于无属主、解除锁定状态。

4、自旋锁类Spinlock

构造函数:pthread_spin_init(&m_mutex, 0); 析构函数:pthread_spin_destroy(&m_mutex);
上锁:pthread_spin_lock(&m_mutex); 解锁:pthread_spin_unlock(&m_mutex);

5、原子锁类CASLock

  • 构造函数: m_mutex.clear(); 重置原子锁的内部状态。原子锁通常是基于硬件原子指令实现的自旋锁,通过比较并交换操作来实现线程间的同步和互斥。
  • 上锁:使用了std::atomic_flag_test_and_set_explicit函数来尝试获取锁,如果锁已经被其他线程持有,那么当前线程就会进入一个忙等待的循环,直到成功获取到锁为止。使用 std::memory_order_acquire 内存顺序保证了锁的正确性和可见性。
void lock() {
        while(std::atomic_flag_test_and_set_explicit(&m_mutex, std::memory_order_acquire));
    }
  • 解锁:通过调用 std::atomic_flag_clear_explicit 函数来释放锁,并使用 std::memory_order_release 内存顺序保证释放操作的可见性。释放锁后,其他被阻塞的线程可以继续尝试获取锁。
void unlock() {
        std::atomic_flag_clear_explicit(&m_mutex, std::memory_order_release);
    }

线程模块

封装线程类thread

设置线程指针和线程名称静态量,创建日志器。

static thread_local Thread *t_thread          = nullptr;//创建线程指针
static thread_local std::string t_thread_name = "UNKNOW";//线程名称
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");//创建日志器

构造函数

初始化列表初始参数;若线程名为空,将其命名为全局静态变量“UNKNOW”,创建线程函数:int pthread_create (pthread_t * tidp, const pthread_attr_t * attr, void * (*start_rtn)(void*), void *arg); 参数1为指向线程标志符的指针,参数2为线程属性,参数3为新线程运行函数的起始地址,参数4为新运行函数的参数;若创建成功,返回0,失败返回出错编号。创建线程后,开始执行&Thread::run函数,创建的新线程对象this作为参数传递给run方法,使得在出构造函数之前,确保线程线跑起来,保证能够初始化id。信号量资源减1,可以使用其进行相关操作。 子线程的执行时机:通过信号量使得构造函数在创建线程后一直阻塞,直到线程函数运行并通知信号量,构造函数才会返回,而构造函数一旦返回,说明线程函数已经在执行了。

Thread::Thread(std::function<void()> cb, const std::string &name)
    : m_cb(cb)
    , m_name(name) {
    if (name.empty()) {//若线程名为空,则命为声明的静态变量名
        m_name = "UNKNOW";
    }
    int rt = pthread_create(&m_thread, nullptr, &Thread::run, this);
    if (rt) {
        SYLAR_LOG_ERROR(g_logger) << "pthread_create thread fail, rt=" << rt
                                  << " name=" << name;
        throw std::logic_error("pthread_create error");
    }
    m_semaphore.wait();//获取信号量资源,可对其进行后续操作,信号量减1
}

析构函数

使用pthread_detach()函数从状态上分离线程(不是指该线程独自占用地址空间);分离成功返回0,失败返回错误号。
pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收。线程分离状态是指定该状态,线程主动与主控线程断开关系,线程结束后(不会产生僵尸线程),其退出状态不由其它线程获取,而直接自己自动释放。

Thread::~Thread() {
    if (m_thread) {//线程存在
        pthread_detach(m_thread);//分离线程,确保资源能够被正确释放而不阻塞其他线程
    }
}

等待线程执行完成

使用pthread_join()函数判断线程是否结束,结束返回0,否则返回非零值,此时在日志器中记录错误并抛出异常;最后设置线程标志为0,表示线程已结束。
pthread_join()将子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。

void Thread::join() {
    if (m_thread) {
        int rt = pthread_join(m_thread, nullptr);//等待线程结束,若返回非零值,表示等待失败
        if (rt) {//等待失败,记录错误并抛出异常
            SYLAR_LOG_ERROR(g_logger) << "pthread_join thread fail, rt=" << rt
                                      << " name=" << m_name;
            throw std::logic_error("pthread_join error");
        }
        m_thread = 0;//设线程标志为0,表示线程已经结束
    }
}

获取当前线程指针及名称

Thread *Thread::GetThis() {
    return t_thread;//返回线程指针
}
const std::string &Thread::GetName() {
    return t_thread_name;//返回线程名称
}

设置当前线程名称

将当前线程名称设置为函数参数name,包括线程指针的名称与静态变量名称。

void Thread::SetName(const std::string &name) {//设置线程名称为name参数
    if (name.empty()) {
        return;
    }
    if (t_thread) {
        t_thread->m_name = name;
    }
    t_thread_name = name;
}

线程执行函数

线程运行后,会使信号量释放占用资源,新线程才可以获取信号量的使用权,构造函数才会返回;在此之前信号量一直被占用,构造函数创建的线程一直被阻塞。若在构造函数中完成线程的启动和初始化,可能会导致线程还未完全初始化就被调用,导致一些未知的问题。

void *Thread::run(void *arg) {
    Thread *thread = (Thread *)arg;
    t_thread       = thread;
    t_thread_name  = thread->m_name;//设置当前线程名称
    thread->m_id   = sylar::GetThreadId();//设置当前线程ID
    pthread_setname_np(pthread_self(), thread->m_name.substr(0, 15).c_str());

    std::function<void()> cb;
    cb.swap(thread->m_cb);//

    thread->m_semaphore.notify();//释放信号量所占用的资源

    cb();
    return 0;
}

基础知识

实现多线程同步的常用方法有4种,分别称为互斥锁、信号量、条件变量和读写锁。

1、信号量semaphore---对进程中某一信号量的有序读写

有两种常见的信号量 API:POSIX 信号量和 System V 信号量;信号量是内核维护的整数变量,通常设置为大于或等于0的整数,可以对信号对象执行两项操作——增加或减少,对应于获取和释放共享资源。

信号量的使用场合

同一时刻有多个进程(线程)对共享内存进行读写时,会出现数据的损坏和丢失;信号量能够保证对该内存的关键访问为原子操作,系统将保证所有的操作依次进行。 其实现方式是以一种类似锁的机制,几个线程(进程)间都可以通过获取同一个信号量的值,判断临界资源是否被信号量“锁住”,进而判断能否读或取。 信号量与互斥锁(mutex)区别 互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区

信号量相关接口函数

POSIX 为未命名的信号量提供了特殊的 sem_t 类型,这是多线程工作流中更常见的工具。sem_t 变量必须使用 sem_init 函数进行初始化,该函数还指示是否应在进程之间或进程的线程之间共享给定的信号量。一旦变量被初始化,就可以使用功能 sem_post 和 sem_wait 来实现同步。sem_post 增加信号量,通常对应于解锁共享资源。相反,sem_wait 减少了信号量并表示资源已锁定。
在linux中定义位置usr/include/semaphore.h,包含头文件#include<semaphore.h>

  • sem_init函数
    int sem_init (sem_t *sem, int pshared, unsigned int value);
    功能:初始化信号量;
    返回值:创建成功返回0,失败返回-1;
    参数:sem为指向信号量结构的指针;pshared不为0时信号量在进程间共享,为0时当前进程的所有线程共享;value为信号量的初始值。
  • sem_post函数
    int sem_post(sem_t * sem)
    功能:给信号量的值加1;
    返回值:操作成功返回0,失败返回-1且置errno;
    参数:sem为指向信号量结构的指针。
  • sem_wait函数
    int sem_wait(sem_t * sem)
    功能:给信号量减1,对于小于0的信号量会一直等到其大于0时再进行减1操作;
    返回值:操作成功返回0,失败返回-1且置errno;
    参数:sem为指向信号量结构的指针。
  • sem_destroy函数
    int sem_destroy(sem_t * sem)
    功能:当没有线程在等待该信号量时释放信号量占用的一切资源;
    返回值:满足条件成功返回0,失败返回-1且置errno;
    参数:sem为指向信号量结构的指针。
  • sem_trywait函数
    int sem_trywait(sem_t * sem)
    功能:为sem_wait的非阻塞版,不进行等待;
    返回值:若信号量值大于0则减1并返回0,否则立即返回-1且置errno为EAGIN;
    参数:sem为指向信号量结构的指针。
  • sem_getvalue函数
    int sem_getvalue(sem_t * sem, int * sval)
    功能:读取sem中信号量计数;
    返回值:操作成功返回0,失败返回-1且置errno;
    参数:sem为指向信号量结构的指针;sval为信号量计数值。

信号量的睡眠特性

信号量的特点是当一个任务试图获取一个已经被占用的信号量,它会被推入等待队列,让其进入睡眠;此刻处理器重获自由,去执行其它代码,当信号量被释放,处于等待队列中的任务被唤醒,并获取该信号量。

  • 适于长时间持有:竞争信号量时,未拿到信号量的进程会进入睡眠。短时间持有会导致开销大,效率低。
  • 只能在上下文进行调用,无法在中断中使用信号量;
  • 不允许持有自旋锁:去占用信号量时可能导致睡眠,而自旋锁不允许睡眠。
    blog.csdn.net/hanshanbule…

2、互斥锁phread_mutex_t---线程访问进程临界资源

互斥锁实现多线程同步的核心思想是:有线程访问进程空间中的公共资源时,该线程执行“加锁”操作(将资源“锁”起来),阻止其它进程访问。访问完成后,该线程负责完成“解锁”操作,将资源让给其它线程。当有多个线程想要访问资源时,谁最先完成“加锁”操作,谁就最先访问资源。
当有多个线程想访问“加锁”状态下的公共资源时,只能等待资源“解锁”,这时所有线程会排成一个等待(阻塞)队列头。资源解锁后,操作系统会唤醒等待队列中的所有线程,第一个访问资源的线程会率先将资源“锁”起来,其它进程则继续等待。
本质上互斥锁是一个全局变量,只有lock和unlock两个值,通过对资源进行加锁(lock)和解锁(unlock)可以确保同一时刻最多有一个线程访问该资源,从根本上避免了“多线程抢夺资源”的情况发生。其含义为:

  • unlock:表示当前资源可以访问,第一个访问该资源的线程负责将互斥锁的值改为“lock”,访问完后再重置为“unlock”;
  • lock:表示当前资源有线程正在访问,其它线程只有等到互斥锁是值变为unlock后才能访问。
    对资源“加锁”和“解锁”操作的必须是同一个线程

互斥锁相关接口函数

POSXI标准规定,使用pthread_mutex_t类型的变量来表示一个互斥锁,该类型结果体的形式定义在头文件<pthread.h>中。 使用mutex的基本步骤是:
(1)、定义mutex pthread_mutex_t myMutex; (2)、初始化互斥锁mutex

  • 静态方式
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;其中pthread_mutex_t是一个结构,PTHREAD_MUTEX_INITIALIZER是一个结构常量的宏。
  • 动态方式
    malloc方式:pthread_mutex_t myMutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init函数:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
    功能:定义互斥锁;
    返回值:定义成功返回0,失败返回-1;
    参数:mutex为pthread_mutex_t互斥锁类型的指针;mutexattr用于指定互斥锁属性的指针,若为NULL则使用default缺省属性PTHREAD_MUTEX_TIMED_NP,其为普通锁(当一个线程加锁时,其余请求锁的线程形成一个等待队列,在解锁后按照优先级获得锁,保障了资源分配的公平性),还可为PTHREAD_MUTEX_RECURSIVE_NP(嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁,若为不同线程请求,则在加锁线程解锁时重新竞争),还可为PTHREAD_MUTEX_ERRORCHECK_NP(检错锁,同一个线程请求同一个锁,返回EDEADLK,否则与普通锁类型操作相同,保证了不允许多次加锁时不会出现最简单情况下的死锁),还可以为 PTHREAD_MUTEX_ADAPTIVE_NP(适应锁,仅等待解锁后重新竞争,为最简单的锁类型)。
    使用结束必须调用pthread_mutex_destroy进行销毁,调用时mutex必须未上锁,如果未销毁该对象会导致内存泄漏。
    不能对一个已经初始化过的互斥锁再进行初始化操作,否则会导致程序出现无法预料的错误。 (3)、使用mutex
  • 加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);
    功能:实现“加锁”操作,会使线程进入等待(阻塞)状态,直至互斥锁得到释放;
  • 测试加锁:int pthread_mutex_trylock(pthread_mutex_t *mutex);
    功能:实现“加锁”操作,但不会阻塞线程,直接返回返回非零数,表示加锁失败;
  • 解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);
    功能:对指定互斥锁进行“解锁”操作;
    返回值:操作成功返回0,否则返回非零数;
    参数:mutex表示要操控的互斥锁。
    (4)、销毁mutex int pthread_mutex_destroy(pthread_mutex_t *mutex);
    功能:销毁创建好的互斥锁;
    返回值:销毁成功返回0,否则返回非零数;
    参数:mutex表示要销毁的互斥锁。
    对于用 PTHREAD_MUTEX_INITIALIZER 或者 pthread_mutex_init() 函数直接初始化的互斥锁,无需调用 pthread_mutex_destory() 函数手动销毁。 c.biancheng.net/view/8631.h…

3、读写锁pthread_rwlock_t---划分访问互斥锁中临界资源的访问者

读写锁实际为一种特殊的自旋锁,将共享资源的访问者划分为只能进行读访问的读者与只能进行写操作的写者。读操作可以共享,写操作是排他的,即可以有多个在读,但只能有一个在写,但又不能同时既有读者又有写者。

  • 写者操作:当读写锁当前没有读者也没有写者,写者立即获得读写锁,否则它必须自旋在哪里,直到没有任何读者和写者;
  • 读者操作:当读写锁没有读者,读者立即获得读写锁,否则只能自旋在那里,直到写者释放该读写锁。
  • 强读者同步:当写者没有写操作,读者就可以访问;
  • 强写者同步:当所有写者都写完之后,才能进行读操作;

特性

一次只有一个线程可以占有写模式的读写锁,可以有多个线程占有读模式的读写锁
当读写锁是写加锁状态时,在该锁被解锁前,所有试图对该锁加锁的线程都会被阻塞;
读加锁的读写锁可以被试图以读模式对其加锁的多个线程访问,此时若有线程希望以写模式对此锁加锁,它必须等到所有线程释放锁才可以,但同时读写锁也会阻塞随后的读模式锁请求,以避免读模式锁长期占用,等待的写模式锁请求长期阻塞;
读写锁适合对数据结构读次数比写次数多得多的情况,因为读模式可以共享,写模式锁住时意味着独占,所以读写锁也称为共享-独占锁

读写锁相关接口函数

(1)初始化
静态方式:pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;不会执行错误检查;
动态方式:int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
功能:初始化读写锁;
返回值:初始化成功返回0,否则返回用于指明错误的错误号;
参数:rwlock为初始化的读写锁,attr定义读写锁的属性,为NULL时为缺省属性,也就是静态方式的实现方法;
读写锁初始化后该锁可以使用任意次数,无需初始化,成功初始化后读写锁的状态会变为已初始化和未锁定;不能初始化已初始化的读写锁,会造成不确定的结果。
(2)获取读写锁中的读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:获取读锁;
返回值:操作成功返回0,否则返回用于指明错误的错误号;
参数:rwlock为要获取的读锁;
若写入器未持有读锁且没有任何写入器基于该锁阻塞,则调用线程会获取读锁;若写入器未持有读锁,但有多个写入器正在等待该锁,调用线程是否获取该锁是不确定的;若某个写入器持有读锁,则调用线程无法获取该锁,从而将其阻塞。
调用线程只有获得该锁后才能将获取读锁命令返回。对于一个线程中持有的多并发读锁时,该线程可以成功调用该命令多次,也必须调用相应次数的解锁命令pthread_rwlock_unlock()才能解除锁定操作。
(3)读取非阻塞读写锁中的锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
功能:获取非阻塞读写锁中的读锁;
返回值:若线程中有写入器持有该锁的读锁或有写入器基于该锁阻塞,则操作失败返回指明错误的错误号;否则操作成功返回0;
参数:rwlock为要获取的读写锁;
(4)写入读写锁中的锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 功能:向rwlock所引用的读写锁应用写锁;
返回值:若没有其它读取线程或写入器持有读写锁rwlock,则调用线程获取写锁,操作成功,返回0;否则返回指向错误的错误号;
参数:rwlock为要获取读写锁;
(5)写入非等待读写锁中的锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
功能:向rwlock所引用的读写锁应用写锁;
返回值:若任何线程当前持有用于读取和写入的rwlock,则操作失败,返回指明错的错误号;否则操作成功,返回0;
参数:rwlock为要获取读写锁;
(6)解除锁定读写锁
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
功能:用来释放在rwlock所引用的读写锁对象所持有的锁;
返回值:操作成功,返回0;否则返回指明错误的错误号;
参数:rwlock为要释放的读写锁;
若调用该命令来释放rwlock中的读锁,且其它读锁当前由该锁对象持有,则该对象会保持读取锁定状态;若该命令释放的是在rwlock中的最后一个读锁,则调用线程不再是该对象的属主,此时该读写锁对象属于无属主、解除锁定状态,对于写锁同理。
(7)销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;//初始化销毁的锁

功能:用来销毁rwlock所引用的读写锁对象并释放该锁使用的任何资源;
返回值:操作成功,返回0;否则返回指明错误的错误号;
参数:rwlock为要销毁的读写锁;
www.cnblogs.com/dins/p/pthr…

4、自旋锁 pthread_spinlock_t

自旋锁与互斥量相似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态,会一直检测锁的状态。

自旋锁适用情况

锁被持有的时间短,线程不希望在重新调度上花费太大的花销;
自旋锁通常用作为底层原语用于实现其它的类型的锁;
当自旋锁变为可用时,CPU不能做其它的任何事情,这也是自旋锁只能被使用一小段时间的原因。

自旋锁相关接口函数

(1). 初始化自旋锁
int pthread_spin_init(pthread_spinlock_t *, int pshared);
pshared的取值可以为
PTHREAD_PROCESS_SHARED : 进程间共享自旋锁(该锁应当分配在共享内存上)。
PTHREAD_PROCESS_PRIVATE : 单个进程内共享。
(2). 自旋锁上锁(阻塞) int pthread_spin_lock(pthread_spinlock_t *);
(3). 自旋锁上锁(非阻塞) int pthread_spin_trylock(pthread_spinlock_t *);
(4). 自旋锁解锁 int pthread_spin_unlock(pthread_spinlock_t *);
(5). 销毁自旋锁 int pthread_spin_destroy(pthread_spinlock_t *); 以上函数成功都返回0.

5、原子锁std::atomic_flag

原子锁是一种特殊的锁,用于保护共享数据的并发访问,确保操作的原子性,即在任何时刻只有一个线程可以访问被保护的数据。相较于传统的互斥锁,原子锁可以在硬件级别上实现操作的原子性,无需显示加锁和解锁,提高了并发访问的效率。 atomic_flag是一个原子的布尔类型,可支持两种原子操作:

  • test_and_set:若atomic_flag对象被设置,则返回true,若未被设置,则将其设置,返回false。

  • clear:清除atomic_flag对象,即将存储标志设为false。

互斥锁与读写锁的区别

当访问临界资源时(访问的含义包括所有的操作:读,写等),需要上互斥锁; 当对数据(互斥锁中的临界资源)进行读取时,需要上读取锁,当对数据进行写入时,需要写入锁;
读写锁的优点:读数据时互斥锁要上锁,而读写锁允许多个线程同时访问,提供了更高的并发度,同时在某个写入者修改数据器件保护该数据,避免其它读者或写入者的干扰。

自旋锁与互斥锁的区别

  • 等待方式不同
    自旋锁:当线程请求自旋锁时,如果锁已被其它线程占用,请求线程将以忙等方式自旋等待,即反复检查锁是否可用,直到获取到锁为止。
    互斥锁:当线程请求互斥锁时,如果锁已被其它线程占用,请求线程将进入阻塞状态,直到获取锁后才会被唤醒继续执行。

  • 资源消耗不同
    自旋锁:自旋等待器件,请求线程一直占用CPU资源,若等待时间较长,则会导致CPU资源浪费;
    互斥锁:阻塞等待器件,请求线程并不占用CPU资源,线程被唤醒后再竞争锁。

  • 适用场景不同
    自旋锁:适用于保护临界区很小且锁的竞争很短暂的情况,因为自旋等待会消耗CPU资源;
    互斥锁:适用于保护临界区很大且锁的竞争很激烈的情况,因为阻塞等待不会占用CPU资源。