线程同步

77 阅读4分钟

线程共享进程的内存地址空间,因此,必须考虑多个线程对共享资源的同步访问,也就是线程同步。线程同步机制有:信号量、互斥锁、条件变量。
在Linux上,信号量有两组,一组是进程间通信的System V IPC信号量,另一组是当前线程同步的POSIX信号量

信号量
信号量函数都是以sem_开头

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);

sem_init初始化一个未命名的信号量,value参数指定信号量的初始值,pshared如果为0,表示信号量是当前进程的局部信号量,否则信号量可以在多个进程之间共享
sem_destroy销毁一个未命名的信号量,sem_init初始化的信号量使用后应该被销毁
sem_wait如果sem信号量值大于0,则信号量减一,函数立即返回。如果值为0,则阻塞等待值非0
sem_trywait相当于非阻塞的sem_wait版本,信号量为0时立即返回-1,并设置错误为EAGAIN
sem_post将信号量的值加一,如果另一个进程或线程等待该信号量,则立刻被唤醒。

示例:

#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem;

void *work(void *arg) {
    sem_wait(&sem);//等待信号量
    printf("arg is %d\n", *(int *)arg);
    return NULL;
}

int main() {
    pthread_t tid;
    int arg = 0;

    int ret = sem_init(&sem, 00);
    assert(ret != -1);

    pthread_create(&tid, NULL, work, (void *)&arg);

    scanf("%d", &arg);

    sem_post(&sem);

    pthread_join(tid, NULL);

    sem_destroy(&sem);

    return 0;
}

程序初始化一个信号量,初值为0,开启一个线程等待信号量,主线程在scanf阻塞,输入完成后信号量加一,线程函数等到信号量,并输出。

互斥锁
互斥锁可以用于保护关键代码段,确保独占式访问,这一点有点像二进制信号量

二进制信号量只能取0和1两个值

互斥锁的相关函数

#include <pthread.h>
int pthread_mutex_init(pthread_mutex *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int Pthread_mutex_trylock(pthread_mutex_t *mutex);

这些函数含义与上面信号量函数含义大同小异。
在使用互斥锁时一定要注意避免死锁
比如:现在有两个互斥锁A和B,主线程将A上锁,线程函数将B上锁,如果主线程现在又想将B上锁,线程函数又想将A上锁,这时,两者都持有一把锁不释放,但同时又想得到另一把锁,也就发生了死锁,谁都无法继续。

条件变量
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程
相关函数:

#include <pthread.h>
int pthread_cond_init(pthread_t_cond_t *cond, const pthread_condattr_t *cond_attr);
int pthread_cond_destory(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast将唤醒所有等待目标条件变量的线程。
pthread_cond_signal将唤醒等待条件变量线程中的一个,至于哪个线程将被唤醒,取决于线程的优先级和调度策略。
pthread_cond_wait函数用于等待目标条件变量。并且配合一个互斥锁使用,防止多个线程同时请求条件变量。在使用pthread_cond_wait之前,mutex应该由本线程加锁,函数执行时,首先把调用线程放入条件变量的等待队列中,然后将互斥锁mutex解锁。因此,从pthread_cond_wait开始执行到其调用线程被放入条件变量的等待队列之间的这段时间内,不会错过目标条件变量的任何变化。当pthread_cond_wait函数成功返回时,互斥锁mutex将再次被锁上。

这里需要明确pthread_cond_wait内部会进行解锁-等待-加锁操作

看一个封装的条件变量类:

class cond {
public:
    cond() {
        if (pthread_mutex_init(&m_mutex, NULL) != 0) {
            throw std::exception();
        }
        if (pthread_cond_init(&m_cond, NULL) != 0) {
            pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }
    ~cond() {
        pthread_mutex_destroy(&m_mutex);
        pthread_cond_destroy(&m_cond);
    }
    bool wait() {
        int ret = 0;
        pthread_mutex_lock(&m_mutex);
        ret = pthread_cond_wait(&m_cond, &m_mutex);
        pthread_mutex_unlock(&m_mutex);
        return ret == 0;
    }
    bool signal() {
        return pthread_cond_signal(&m_cond) == 0;
    }
private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
};

以上便是线程同步的几种方法。在实际编程中可能会遇到各种各样的问题,这就需要我们经常翻阅手册,对它有足够的了解,才能对症下药。加油吧!!!

参考资料
[1] 游双.Linux高性能服务器编程[M].北京:机械工业出版社,2013.
[2] 祝洪凯,李妹芳,付途译.Linux系统编程[M].北京:人民邮电出版社,2014.