linux线程安全

线程安全:

  1. 多个线程同时对操作临界资源而不会出现数据二义性
  2. 在线程中对临界资源进行了非原子操作

      可重入/不可重入 :多个执行流同时进入函数中运行而不会出现问题

如何实现线程安全:

  1. 同步:临界资源的合理访问
  2. 互斥:临界资源的同一时间访问

互斥的实现: 

     互斥锁:0/1计数器

                 1表示可以加锁,加锁后-1

                 操作完了之后要解锁,解锁后 +1

                 0表示不可以加锁,不能加锁则等待

     互斥锁的操作步骤

互斥锁的实现:简单的黄牛抢票

  1. 定义 互斥锁变量:   pthread_mutex_t 
  2. 初始化互斥锁变量:pthread_mutex_init
  3. 加锁 :                     pthread_mutex_lock
  4. 解锁                          pthread_mutex_unlock
  5. 销毁互斥锁               pthread_mutex_destroy
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
int ticket=10000;
pthread_mutex_t mutex;
void* thr_start(void* arg){
    while(1){
    //加锁一定要在访问临界资源之前
    //int pthread_mutex_lock(pthread_mutex_t* mutex);
    //阻塞加锁,加不上就阻塞    
    //int pthread_mutex_trylock(pthread_mutex_t* mutex);
    //尝试加锁,加不上锁直接报错返回
    //int pyhread_mutex_timelock(pthread_mutex_t* mutex);
    //限时阻塞加锁
    pthread_mutex_lock(&mutex);
    if(ticket>0){
        sleep(1);
        printf("yellow bull---%d---get a ticket is %d \n",(int)arg,ticket);
        ticket--;
    }else{
        //在任何有可能的地方要解锁
        pthread_mutex_unlock(&mutex);
        pthread_exit(NULL);

    }

    pthread_mutex_unlock(&mutex);

    }
    return NULL;
}
int main(){
    pthread_t tid[4];
    int i=0,ret;
    //int pthread_mutex_init(pthread_t thread,pthread_mutexattr_t* attr);
    //thread:互斥锁变量
    //attr:属性,通常置NULL
    pthread_mutex_init(&mutex,NULL);
    for(i=0;i<4;++i){
        int ret=pthread_create(&tid[i],NULL,thr_start,(void*)i);
        if(ret!=0){
            printf("yellow %d is not exist\n",i);
            return -1;
        }
    }
    for(i=0;i<4;++i){
        //此时不关心返回值 置NULL就可以
        pthread_join(tid[i],NULL);
    }
    //int pthread_mutex_destroy(pthread_mutex_t* mutex );
    //mutex : 互斥锁变量
    pthread_mutex_destroy(&mutex);
    return 0;
}

 

同步的实现:

         临界资源访问的合理性-------生产出来才能使用----等待+唤醒。没有资源一直死等,生产资源出来后唤醒等待

         条件变量:

                        1,定义条件变量:pthread_cond_t;

                        2,初始化条件变量:pthread_cond_init;

                        3,等待/唤醒:         pthread_cond_wait/pthread_cond_signal

                        4,销毁条件变量:  pthread_cond_destroy

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<errno.h>
pthread_cond_t boss;
pthread_cond_t customer;
pthread_mutex_t mutex;
int have_noodle=1;
void* thr_boss(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        //若面没有卖出去,则等待
        while(have_noodle==1){
            //int pthread_cond_timedwait(pthread_cond_t* cond,
            //pthread_mutex_t* mutex,struct timespec* abstime);
            //cond :条件变量
            //mutex:互斥锁
            //abstime:显示等待时长,
            //int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
            //死等  原子操作不可被打断
            //pthread_cond_wait 中集合了先解锁--唤醒---唤醒后加锁
            //若有面先解锁然后一直等待被人唤醒
            //若被唤醒则唤醒后加锁
            pthread_cond_wait(&boss,&mutex);
        }
        printf("noodle has been complete\n");
        have_noodle+=1;
        //面做好了,唤醒顾客
        pthread_cond_signal(&customer);
        //解锁
        pthread_mutex_unlock(&mutex);
        }
        return NULL;

}
void* thr_customer(void* arg){
    while(1){
        //第一件事情先加锁
        pthread_mutex_lock(&mutex);
        while(have_noodle==0){
                pthread_cond_wait(&customer,&mutex);

        }
    //接下来吃面
    printf("noodle is delicious\n");
    have_noodle-=1;
    //你吃完了就要唤醒老板再做一碗
    pthread_cond_signal(&boss);
    //解锁
    pthread_mutex_unlock(&mutex);
    }

    return NULL;

}
int main(){
    pthread_t tid1,tid2;
    pthread_cond_init(&boss,NULL);
    pthread_cond_init(&customer,NULL);
    pthread_mutex_init(&mutex,NULL);
    int i=0,ret;
    for(i=0;i<2;++i){
        ret=pthread_create(&tid1,NULL,thr_boss,NULL);
        if(ret!=0){
            printf("boss error\n");
            return -1;
        }
    }
    for(i=0;i<2;++i){
        ret=pthread_create(&tid2,NULL,thr_customer,NULL);
        if(ret!=0){
            printf("customer error\n");
            return -1;
        }
    }
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_cond_destroy(&boss);
    pthread_cond_destroy(&customer);
    pthread_mutex_destroy(&mutex);
    return 0;
    }

            信号量:计数器+等待队列+等待+唤醒

                           功能:实现线程/进程间同步与互斥

                           计数器就是判断条件----当计数器只有0/1的时候就实现了互斥

                           等待队列+等待+唤醒实现同步的功能

           system V信号量:信号量原语:p(-1+阻塞)  /  v(+1+唤醒)操作

           prosix信号量:

                          定义:sem_t  信号量变量

                          初始化:sem_init

                          数据操作前资源计数判断:sem wait

                                    计数>0则计数-1,直接返回,往下操作

                                    计数<=0则阻塞等待

                          生产数据后+1          sem_post

                          销毁:       sem_destroy

  1 #include<iostream>
  2 #include<pthread.h>
  3 #include<semaphore.h>
  4 #include<vector>
  5 class RingQueue{
  6     public:
  7     //对信号量初始化
  8     RingQueue(int cap=10):_queue(10),_capacity(cap),_write_step(0),_read_step(0){
  9         //int sem_init(sem_t* sem,int pshared,unsigned int value);
 10         //sem:信号量变量
 11         //pshared:选项标志,决定信号量用于线程间还是进程间的同步与互斥
 12         //        0:线程间
 13         //        !0:进程间
 14         //value: 信号量初始计数
 15         sem_init(&_sem_data,0,0);
 16         sem_init(&_sem_idle,0,cap);
 17         sem_init(&_sem_lock,0,1);
 18     }
 19     //销毁信号量
 20     ~RingQueue(){
 21     //int sem_destroy(sem_t* sem);
 22     sem_destroy(&_sem_data);
 23     sem_destroy(&_sem_idle);
 24     sem_destroy(&_sem_lock);
 25     }
 26     //将用户传进来的数据放到环形队列当中
 27     bool QueuePush(int data){
 28         //首先再放入数据时需要判断是否满足条件
 29         ProductorWait();
 30         //队列操作进行上锁
 31         QueueLock();
 32         //将数据放入队列当中
 33         _queue[_write_step]=data;
 34         //因为是循环队列,所以
 35         _write_step=(_write_step+1) % _capacity;
 36         //在对队列进行解锁
 37         QueueUnLock();
 38         //然后唤醒消费者
 39         ConsumerWakeUp();
 40         return true;
 41     }
 42     bool QueuePop(int* data){
 43         //消费者等待
 44         ConsumerWait();
 45         //上锁
 46         QueueLock();
 47         //读取数据
 48         *data=_queue[_read_step];
 49         //修改当前下标
 50         _read_step=(_read_step+1) % _capacity;
 51         QueueUnLock();
 52         ProductorWakeUp();
 53         return true;
 54     }
 55     private:
 56     void QueueLock(){
 57     //通过计数判断是否有资源可用,如果有计数-1+等待
 58     //int sem_wait(sem_t* sem);
 59     //若计数<=0则阻塞
 60     //int sem_trywait(sem_t* sem);
 61     //若
 62     //计数<=则报错返回
 63     //int sem_timewait(sem_t* sem,struct timespec* abs_timeout);
 64     //若计数<=0则限时等待,超时则报错返回
 65     //通过用信号量计数的方式实现互斥锁 原子操作
 66     sem_wait(&_sem_lock);
 67 
 68     }
 69     void QueueUnLock(){
 70     //计数+1,并唤醒等待
 71     //int sem_post(sem_t* sem);
 72     //计数+1,唤醒互斥锁信号
 73     sem_post(&_sem_lock);
 74 
 75     }
 76     void ProductorWait(){
 77         //当空闲数据<=0时,阻塞等待
 78         sem_wait(&_sem_idle);
 79     }
 80     void ProductorWakeUp(){
 81         //当空闲位置+1时唤醒生产者
 82         sem_post(&_sem_idle);
 83     }
 84     void ConsumerWait(){
 85         //当数据<=0时阻塞等待
 86         sem_wait(&_sem_data);
 87     }
 88     void ConsumerWakeUp(){
 89         //当有数据产生时唤醒消费者
 90         sem_post(&_sem_data);
 91     }
 92     private:
 93     std::vector<int> _queue;
 94     int _capacity;
 95     int _write_step;
 96     int _read_step;
 97     sem_t _sem_data;//数据资源计数
 98     sem_t _sem_idle;//空闲空间计数
 99     sem_t _sem_lock;//实现互斥
100 
101 };
102 //生产者线程入口函数
103 void* thr_productor(void* arg){
104     int i=0;
105     RingQueue* q=(RingQueue*)arg;
106     while(1){
107         q->QueuePush(i);
108         std::cout<<"productor thread put data is "<<i<<std::endl;
109         i++;
110     }
111     return NULL;
112 }
113 //消费者入口函数
114 void* thr_consumer(void* arg){
115     RingQueue* q=(RingQueue*)arg;
116     while(1){
117         int data;
118         q->QueuePop(&data);
119         std::cout<<"consumer thread read data is "<<data<<std::endl;
120     }
121     return NULL;
122 }
123 int main(){
124     pthread_t pro[4],con[4];
125     RingQueue q;
126     int ret,i;
127     for(i=0;i<4;++i){
128         ret=pthread_create(&pro[i],NULL,thr_productor,(void*)&q);
129         if(ret!=0){
130             std::cout<<"thread is error"<<std::endl;
131             return -1;
132         }
133     }
134     for(i=0;i<4;++i){
135         ret=pthread_create(&con[i],NULL,thr_consumer,(void*)&q);
136         if(ret!=0){
137             std::cout<<"thread is error"<<std::endl;
138             return -1;
139         }
140     }
141     for(i=0;i<4;++i){
142         pthread_join(pro[i],NULL);
143     }
144 
145     for(i=0;i<4;++i){
146         pthread_join(con[i],NULL);
147     }
148     return 0;
149 }

线程安全的单例模式

饿汉方式

template <typename T>
class Singleton {
    static T data;
public:
    static T* GetInstance() {
        return &data;
    }
};

懒汉方式

非安全版本

template <typename T>
    class Singleton {
    static T* inst;
public:
    static T* GetInstance() {
        if (inst == NULL) {
            inst = new T();
        }
    return inst;
    }
};

安全版本

/ 懒汉模式, 线程安全
template <typename T>
    class Singleton {
    volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
    static std::mutex lock;
public:
    static T* GetInstance() {
        if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
            lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
                if (inst == NULL) {
                    inst = new T();
                }
            lock.unlock();
        }
        return inst;
    }
};

STL中的容器是否是线程安全的?

不是.
原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

其他常见的锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值