多线程(2)---线程安全

线程安全:

在多个执行流当中对同一个临界资源进行操作访问,而不会造成数据二义。

通过同步与互斥实现线程安全

  • 互斥:通过保证同一时间只有一个执行流就可以对临界资源进行访问,一个执行流访问期间,而其他执行流不能访问,来保证数据访问的安全性;通过互斥锁实现。
  • 同步:通过一些条件判断来实现执行流对临界资源访问的合理性,有临界资源则访问,,没有则等待,等有了资源再被唤醒;通过条件变量,POSIX标准的信号量实现。

1、互斥锁:

有一个计数器,0(不可访问),1(可访问),初值为1;
每一个线程在访问临界资源之前,先判断计数器的值和当前临界资源的状态(是否有人正在访问);
若可以访问,就将计数器置为0,然后去访问资源;
其他线程在访问这个临界资源的时候,发现不可访问,就陷入等待(将PCB置为可中断休眠状态,则不被CPU所调度);
这个线程访问完毕后,将计数器置为1,允许别的线程访问,并唤醒一个线程(将PCB置为运行态,CPU开始调度)。

#include<pthread.h>
pthread_mutex_t ---互斥锁变量类型(一个结构体)
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//动态初始换互斥锁(较常用)
//restrict mutex---互斥锁变量的地址
//restrict attr---互斥锁的属性(通常不设置,置NULL)
//返回值---成功返回 0;失败返回非 0值(errno)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//静态初始化互斥锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
//在临界资源访问之前加锁,阻塞加锁(若临界资源已经加锁,则阻塞,直至临界资源可以加锁为止)    
int pthread_mutex_trylock(pthread_mutex_t* mutex);
//非阻塞加锁(若临界资源已经加锁,直接退出)    
int pthread_mutex_timelock(pthread_mutex_t* mutex);
//限时阻塞加锁(若临界资源的已经加锁,则阻塞某个时间段,还不能加锁就退出)   
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//在临界资源访问之后解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//销毁互斥锁

利用上面接口简单模拟实现一个黄牛抢票的demo:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int tickets = 100;//共有100张票

void* thread_grabbing(void* mutex)
{
    while(1)
    {
        pthread_mutex_lock((pthread_mutex_t*) mutex);//阻塞加锁,
        //pthread_mutex_trylock((pthread_mutex_t*)mutex);//非阻塞加锁
        //pthread_mutex_timelock((pthread_mutex_t*)mutex);限时等待阻塞加锁
        if(tickets > 0)
        {
            printf("scalper:%p- get a tickets:%d\n",pthread_self(),tickets);//抢到一张票
            tickets--;
            pthread_mutex_unlock((pthread_mutex_t*) mutex);//抢完一张票解锁,下次和其他黄牛再次竞争抢票
            usleep(1);
			//为了保持抢票的公平性,让刚抢完票的这个黄牛稍微停一下
        }

        else//票没了就退出
        {
            printf("scalper:%p exit\n",pthread_self());
            pthread_mutex_unlock((pthread_mutex_t*) mutex);//在任何线程可能退出的地方都要解锁
            pthread_exit(NULL);
        }
    }
    return NULL;    
}

int main()
{
    int i,ret;
    pthread_t scalper[4];//四个线程代表四个黄牛
    pthread_mutex_t mutex;//定义互斥锁
    pthread_mutex_init(&mutex,NULL);
    
    for(i = 0;i < 4;i++)
    {
        ret = pthread_create(&(scalper[i]),NULL,thread_grabbing,&mutex);//创建四个黄牛线程
        if(ret != 0)
        {
            printf("thread create error\n");
            return -1;
        }
    }

    for(i = 0;i < 4;i++)
    {
         pthread_join(scalper[i],NULL);//等待线程退出,释放资源;
    }
    pthread_mutex_destroy(&mutex);//销毁互斥锁
    return 0;
}

2、条件变量:

向用户提供两个接口,使一个线程等待的接口和唤醒一个线程的接口 +等待队列;条件变量只是向用户提供了等待与唤醒的功能,但是什么时候等待,什么时候唤醒,需要用户自己来做判断

#include <pthread.h>
pthread_cond_t cond;条件变量类型(一个结构体)
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//动态初始化条件变量(较常用)
//restrict cond---条件变量的地址
//restrict attr---条件的属性(通常不设置,置NULL)
//返回值---成功返回 0;失败返回非 0值(errno)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//静态初始化条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//使当前调用的执行流陷入等待
//restrict cond---条件变量的地址
//restrict mutex---互斥锁变量的地址
int pthread_cond_broadcast(pthread_cond_t *cond);
//广播唤醒所有等待的执行流
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒至少一个条件变量等待队列中的执行流
int pthread_cond_destroy(pthread_cond_t *cond);
//销毁条件变量
发布了77 篇原创文章 · 获赞 16 · 访问量 6468

猜你喜欢

转载自blog.csdn.net/weixin_43886592/article/details/104030316