美文网首页C++多线程
Linux系统编程9:多线程同步

Linux系统编程9:多线程同步

作者: jdzhangxin | 来源:发表于2018-05-05 14:16 被阅读65次

多线程同步主要有信号量、互斥量、条件变量和读写锁四种方式。

0. 背景

竞争

#include <stdio.h>
#include <pthread.h>
 
void* func(void* arg){
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
}
 
int main(int argc,int argv[]){  
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
}

1. 信号量

1.1 操作

No. 操作 函数
1 创建 int sem_init(sem_t *sem, int pshared, unsigned int value)
2 销毁 int sem_destroy(sem_t *sem)
3 阻塞等待 int sem_wait(sem_t *sem)
4 非阻塞等待 int sem_trywait(sem_t * sem)
5 触发 int sem_post(sem_t *sem)

1.1.1 创建

int sem_init(sem_t *sem, int pshared, unsigned int value)
No. 参数 含义
1 sem 信号量对象
2 pshared 信号量类型。0:线程共享;<0:进程共享
3 value 初始值
  • 返回值
No. 返回值 含义
1 0 成功
1 -1 失败

1.1.2 销毁

int sem_destroy(sem_t *sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.2 等待

1.2.1 阻塞等待

int sem_wait(sem_t *sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.2.2 非阻塞等待

int sem_trywait(sem_t * sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.3 触发

int sem_post(sem_t *sem) 
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败
  • 示例
    解决竞争
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
 
void* func(void* arg){
    sem_wait(arg);
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
    sem_post(arg);
}
 
int main(int argc,int argv[]){
    sem_t sem;
    sem_init(&sem,0,1);
     
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&sem);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    sem_destroy(&sem);
 
}

2. 互斥量

  • 比喻
    ATM取款
    toilet

2.1 分类

No. 分类 实现 特点
1 静态分配互斥量 pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER; 简单
2 动态分配互斥量 pthread_mutex_init(&mutex, NULL);pthread_mutex_destroy(&mutex); 可以设置更多的选项

2.2 操作

No. 操作 函数
1 加锁 int pthread_mutex_lock(pthread_t *mutex)
2 尝试加锁 int pthread_mutex_trylock(pthread_t *mutex)
3 解锁 int pthread_mutex_unlock(pthread_t *mutex)
  • 参数
No. 参数 含义
1 mutex 互斥锁
  • 示例
#include <stdio.h>
#include <pthread.h>
 
void* func(void* arg){
    pthread_mutex_lock(arg);
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
    pthread_mutex_unlock(arg);
}
 
int main(int argc,int argv[]){
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,NULL);
     
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
}

更加安全的做法

#include <stdio.h>
#include <pthread.h>
 
void* func(void* arg){
    pthread_cleanup_push(pthread_mutex_unlock,arg);
 
    pthread_mutex_lock(arg);
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
    pthread_exit(0);
 
    pthread_cleanup_pop(0);
}
 
int main(int argc,int argv[]){
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,NULL);
     
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
}

3 条件变量

  • 概念
    线程挂起直到共享数据的某些条件得到满足

3.1 分类

No. 分类 实现 特点
1 静态分配条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 简单
2 动态分配静态变量 pthread_cond_init(&cond, NULL);pthread_cond_destroy(&cond); 可以设置更多的选项

3.2 操作

No. 操作 函数
1 条件等待 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
2 计时等待 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
1 单个激活 int pthread_cond_signal(pthread_cond_t *cond)
2 全部激活 int pthread_cond_broadcast(pthread_cond_t *cond)

3.2.1 等待

3.2.1.1 条件等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
No. 参数 含义
1 cond 条件变量
2 mutex 互斥锁
3.2.1.2 计时等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
No. 参数 含义
1 cond 条件变量
2 mutex 互斥锁
3 abstime 等待时间
  • 返回值
No. 返回值 含义
1 ETIMEDOUT 超时结束等待

3.2.2 激活

3.2.2.1 单个激活
int pthread_cond_signal(pthread_cond_t *cond)
No. 参数 含义
1 cond 条件变量
  • 返回值
No. 返回值 含义
1 0 成功
2 正数 错误码
3.2.2.2 全部激活
int pthread_cond_broadcast(pthread_cond_t *cond)
No. 参数 含义
1 cond 条件变量
  • 返回值
No. 返回值 含义
1 0 成功
2 正数 错误码

套路

条件变量一般与互斥锁一起使用。

  • 条件变量发送信号
pthread_mutex_lock(&mutex);
 
// do something
if(判断条件){
    pthread_cond_signal(&cond);// 唤醒单个
    // 或者
    pthread_cond_broadcast(&cond);// 唤醒多个
}
 
pthread_mutex_unlock(&mutex);
  • 条件变量等待信号
pthread_mutex_lock(&mutex);
 
while(判断条件){
    pthread_cond_wait(&cond,&mutex);
}
// do something
// 把判断条件改为false
pthread_mutex_unlock(&mutex);

流程分析

  • 主线程:等待子线程发信号。
  • 子线程:每隔3秒计数一次,当数字是3的倍数时通知主进程。
#include <pthread.h>
#include <unistd.h>
#include <iostream>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
bool condition = false;

#define LOG(msg) cout << __func__<< ":" << msg << endl;

void* ChildFunc(void*){
    int i = 0;
    while(true){
        LOG("Enter");
        pthread_mutex_lock(&mutex);
        LOG("Get Lock");
        cout << ++i << endl;
        if(0 == i%3){
            condition = true;
            LOG("Begin Send Single");
            pthread_cond_signal(&cond);// 唤醒单个
            LOG("End Send Single");
        }
        sleep(3);
        pthread_mutex_unlock(&mutex);
        LOG("Lose Lock");
        LOG("Leave");
    }
}
void MainFunc(){
    while(true){
        LOG("Enter");
        pthread_mutex_lock(&mutex);
        LOG("Get Lock");
        while(!condition){
            LOG("Begin Wait Single");
            pthread_cond_wait(&cond,&mutex);
            LOG("End Wait Single");
        }
        condition = false;
        pthread_mutex_unlock(&mutex);
        LOG("Lose Lock");
        LOG("Leave");
    }
}

int main(){
    pthread_t tid;
    pthread_create(&tid,NULL,ChildFunc,NULL);
    MainFunc();
    pthread_join(tid,NULL);
    return 0;
}

问题:

  • 主线程在pthread_cond_wait()时,是否释放互斥锁?
  • 主线程在什么时候重新获得互斥锁?

案例

老板与会计的约定:每笔超过1000元的支出必须老板批准同意,低于1000元的会计可以自行决定。

#include <stdio.h>
#include <pthread.h>
#include <signal.h>
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
int currency = 0 ;
int signal_cnt = 0;
 
void* checkout(void* arg){
    sleep(1);
    for(;;){
        pthread_mutex_lock(&mutex);
        printf("checkout enter\n");
        currency = rand()%2000;     
        printf("spend %d\n",currency);
        if(currency >= 1000){
            printf("\033[42;31msignal boss:%d\033[0m\n");
            pthread_cond_signal(&cond);
            signal_cnt++;
        }
        printf("checkout leave\n");
        pthread_mutex_unlock(&mutex);
        //sched_yield();
        sleep(1);
    }
}
 
void* boss(void* arg){
    for(;;){
        pthread_mutex_lock(&mutex);
        printf("boss enter\n");
        while(currency < 1000){
            printf("boss wait\n");
            pthread_cond_wait(&cond,&mutex);
        }
        signal_cnt--;
        printf("\033[46;31mboss agress:%d signal_cnt:%d\033[0m\n",currency,signal_cnt);
        currency = 0;
        printf("boss leave\n");
        pthread_mutex_unlock(&mutex);
    }
}
 
int main(){
    typedef void*(*func_t)(void*);
    func_t funcs[2] = {boss,checkout};
    pthread_t tids[2];
    int i;
    pthread_setconcurrency(2);
    for(i=0;i<2;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<2;i++){
        pthread_join(tids[i],NULL);
    }   
}

问题

  • 互斥锁和条件变量能否不作为全局变量?

4. 读写锁

资源访问分为两种情况:读操作和写操作。

读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。

  1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
  2. 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;
  3. 当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞;
    这种锁适用对数据结构进行读的次数比写的次数多的情况下,因为可以进行读锁共享。
  • 比喻

新闻发布会
领导发言与聊天

  • 概念
    共享独占
    读取锁(共享)
    写入锁(独占)

4.1 分类

No. 分类 实现 特点
1 静态分配读写锁 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER 简单
2 动态分配读写锁 pthread_rwlock_init(&rwlock, NULL);pthread_rwlock_destroy(&rwlock); 可以设置更多的选项

4.2 操作

4.2.1 加锁

4.2.1.1 读取锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

4.2.1.2 写入锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

4.2.2 解锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
  • 示例

火车票的查询与购买

#include <stdio.h>
#include <pthread.h>
 
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int count = 10000;
int put_cur = 0;
void* search(void* arg){
    for(;;){
        pthread_rwlock_rdlock(&rwlock);
        printf("leave %d\n",count); 
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
}
void rollback(void* arg){
    count -= put_cur;
    printf("rollback %d  to %d\n",put_cur,count);
    pthread_rwlock_unlock(&rwlock);
}
void* put(void* arg){
    pthread_cleanup_push(rollback,NULL);
    for(;;){
        pthread_rwlock_wrlock(&rwlock);
        put_cur = rand()%1000;
        count += put_cur;
        printf("put %d ok,leave %d\n",put_cur,count);
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
    pthread_cleanup_pop(0);
}
void* hacker(void* arg){
    sleep(2);
    pthread_t* ptids = arg;
    pthread_cancel(ptids[0]);
    printf("\033[41;34mcancel %lu\033[0m\n",ptids[0]);
}
void* get(void* arg){
    for(;;){
        pthread_rwlock_wrlock(&rwlock);
        int cur = rand()%1000;
        if(count>cur){
            count -= cur;
            printf("crash %d leave %d\n",cur,count);
        }else{
            printf("leave not enought %d\n",count);
        }
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
}
 
int main(){
    pthread_t tids[4];
    typedef void*(*func_t)(void*);
    func_t funcs[4] = {put,get,search,hacker};
    int i=0;
    pthread_setconcurrency(4);
    for(i=0;i<4;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<4;i++){
        pthread_join(tids[i],NULL);
    }
}

相关文章

  • Linux系统编程9:多线程同步

    多线程同步主要有信号量、互斥量、条件变量和读写锁四种方式。 0. 背景 竞争 1. 信号量 1.1 操作 1.1....

  • 多线程编程

    多线程编程之Linux环境下的多线程(一)多线程编程之Linux环境下的多线程(二)多线程编程之Linux环境下的...

  • 线程同步与互斥

    Linux--线程编程 多线程编程-互斥锁 线程同步与互斥 互斥锁 信号量 条件变量 互斥锁 互斥锁的基本使用...

  • Python 多线程编程

    多线程编程 进程(process)和线程(thread) Linux 和 windows 是多任务操作系统, 这就...

  • mutex lock 唤醒顺序

    在 Linux 多线程编程中,我们常常用 pthread_mutex_lock 来做线程间同步。当锁被占用时,当前...

  • Linux系统编程:多线程编程

    一、什么是线程? 线程是一种执行流,也是一种任务流,因此线程也具有5个状态,可参与时间片轮转; 线程必须依附于进程...

  • 多线程编程

    参考:C++ 并发编程 线程 windsows多线程 new thread(...) linux 多线程: pth...

  • linux系统编程环境配置

    Windows 10系统下Linux子系统如何配置系统编程环境 主要针对《Linux/Unix系统编程手册》代码 ...

  • Linux 中的线程局部存储(1)

    在Linux系统中使用C/C++进行多线程编程时,我们遇到最多的就是对同一变量的多线程读写问题,大多情况下遇到这类...

  • Android NDK开发之旅33--NDK-Linux入门之P

    POSIX POSIX是一种标准,例如有多线程编程标准、网络编程标准等。 POSIX多线程 Linux下,一般多线...

网友评论

本文标题:Linux系统编程9:多线程同步

本文链接:https://www.haomeiwen.com/subject/nrrmhftx.html