Linux:浅析线程(线程的同步与互斥)

线程的同步与互斥

互斥量(mutex)

在这里,我们先看一个代码。

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

int ticket = 100;

void* BuyTicket(void* arg)
{
  char* msg = (char*)arg;
  while(1) {
    if(ticket > 0) {
      usleep(1000);
      printf("%sBuy ticket,have tickets %d\n",msg, ticket);
      ticket--;
    } else {
      break;
    }
  }
  return (void*)0;
}

int main()
{
  pthread_t tid1, tid2, tid3, tid4;

  pthread_create(&tid1, NULL, BuyTicket, (void*)"thread1");
  pthread_create(&tid2, NULL, BuyTicket, (void*)"thread2");
  pthread_create(&tid3, NULL, BuyTicket, (void*)"thread3");
  pthread_create(&tid4, NULL, BuyTicket, (void*)"thread4");

  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  pthread_join(tid3, NULL);
  pthread_join(tid4, NULL);

  return 0;
}

这里写图片描述
我们发现,票数竟然还有负的,这显然与我们预计的不同。那是什么原因呢?我们创建四个线程,这四个线程之间互相争抢ticket,每抢一张后,会将总票数减减,直至抢完。此时由于我们对ticket的减减并不是原子操作,(首先将全局变量ticket从内存加载到寄存器中,接着减一更新寄存器里面的值,最后将更新过后的新值放入内存)而我们在线程执行“抢票”操作的时候,引入usleep,如果不usleep,可能票直接就被第一个线程全部抢光了。所以引入usleep是为了在usleep的时候让别的线程可以进入,达到所有线程都在争抢这个临界资源ticket。

相当于100张票,线程一拿的时候是100张,然后在线程一还没完成抢票的时候,线程二完成抢票此时ticket剩余99张,但是线程一不知道,所以它默认100张票,它抢完以后还剩99,它将99放入内存。这时候相当于抢了两张票,但是ticket只减了一,这时候就出现了问题。

所以这时候我们要解决这个问题就要做到:当某一个线程访问临界资源的时候,不能让其他的线程再次访问临界资源,一次有且只能有一个线程去访问临界资源。所以相当于有一把锁,任意一个线程在访问临界资源时,首先申请锁,如果申请到锁,那么它将访问临界资源,并且其他进程无法申请到锁,只能进行阻塞等待,直至申请到锁的线程访问完毕,并且释放锁,其他进程才可申请到锁。Linux上提供的这把锁就叫做互斥量。


互斥量的基本函数:

pthread_mutex_t mutex;//定义互斥量

int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);
//功能:初始化互斥量
//参数:mutex要初始化的互斥量,attr:NULL

int pthread_mutex_destroy(pthread_mutex_t* mutex);
//功能:销毁互斥量

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
//互斥量加锁与解锁

销毁互斥量注:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,在后面是不能尝试再次加锁

加锁注:

  • 在线程申请互斥量时,其他线程已经申请并加锁,未解锁,或未竞争过其它线程时,那么pthread_lock调用会陷入阻塞,等待互斥量解锁。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int ticket = 100;

pthread_mutex_t mutex;//定义互斥量

void* BuyTicket(void* arg)
{
  char* msg = (char*)arg;
  while(1) {
    pthread_mutex_lock(&mutex);//加锁
    if(ticket > 0) {
      usleep(1000);
      printf("%sBuy ticket,have tickets %d\n",msg, ticket);
      ticket--;
    pthread_mutex_unlock(&mutex);//解锁并释放
    } else {
      pthread_mutex_unlock(&mutex);//解锁并释放
      break;
    }
  }
  return (void*)0;
}

int main()
{
  pthread_t tid1, tid2, tid3, tid4;
  pthread_mutex_init(&mutex, NULL);//初始化互斥量

  pthread_create(&tid1, NULL, BuyTicket, (void*)"thread1");
  pthread_create(&tid2, NULL, BuyTicket, (void*)"thread2");
  pthread_create(&tid3, NULL, BuyTicket, (void*)"thread3");
  pthread_create(&tid4, NULL, BuyTicket, (void*)"thread4");

  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  pthread_join(tid3, NULL);
  pthread_join(tid4, NULL);

  return 0;
}

这里写图片描述
我们发现假如互斥锁以后,这个时候我们之前出的问题就不复存在了。

条件变量

前面的互斥锁目的是在某个线程访问临界资源的时候,在它没有访问完毕释放锁之前,其它线程是无法申请到互斥锁的。倘若现在的情景不是在买票,而是在超市购物,消费者去超市购物,想要买盒酸奶,到了货架上发现酸奶没有了。这时候消费者就会问促销员有没有酸奶。促销员说没有。如果这时候消费者不停的一直问一直问,相当于线程不断的申请到锁访问临界资源。那么作为促销员他都没有时间去仓库通知上货。那这种情况只能是永无止境的问,永无止境的等待。
相当于一个线程在临界资源中存放数据,而另一个线程在临界资源中读取数据。而读取数据的线程优先级更高,它申请到锁的可能更大。如果此时临界资源没有数据可以读,但是由于线程优先级高,它不断的申请锁释放锁,但是实际上它并没有拿到任何资源。并且存放数据的线程还申请不到锁,无法进去放置资源,这时候两个线程都做不了自己要做的事情,这种情况就叫做发生了死锁问题。
那么如何避免这个问题呢?这时候就需要引进条件变量。
条件变量的作用就是在读数据的线程申请到锁进入临界资源,发现并没有数据是。这时候触发条件变量,释放锁,并挂起等待。直到放数据的线程申请到锁,并存放数据完毕后,唤醒读数据的线程。依次循环。

条件变量函数

int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);
//功能:初始化条件变量
//参数:cond:要初始的条件变量
//attr:NULL


int pthread_cond_destroy(pthread_comd_t* cond);
//功能:销毁条件变量


int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);
//功能:等待条件满足
//参数:cond:要在这个条件变量下等待
//mutex:互斥量,要等待的互斥量


int pthread_cond_broadcat(pthread_cond_t* cond);//唤醒一群线程
int pthread_cond_signal(pthread_cond_t* cond);//唤醒某一个线程

在验证条件变量的使用之前,先引入一个叫做生产者消费者模型。生产者是作为产生资源,消费者是取走资源。有一片区域是提供的交易场所。生产者与消费者在这片区域交易。同时如果有很多生产者,那么这些生产者之间的关系就是互斥,而消费者与消费者之间也是互斥。每个生产者与单个消费者之间的关系是互斥且同步。所以是三种关系两个角色一个交易场所。引入生产者消费者模型后,我们利用这个来验证一下条件变量。

#include <stdio.h>
#include "seqqueue.h"
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void* consumers(void* arg)
{
  SeqQueue* q = (SeqQueue*)arg;
  char top = '\0';

  while(1) {
    pthread_mutex_lock(&mutex);
    usleep(500);  
    int ret = SeqQueueGetFront(q, &top);
    if(ret == -1) {
      printf("can not get\n");
      //pthread_cond_wait(&cond, &mutex);
      pthread_mutex_unlock(&mutex);
    } else {
      SeqQueuePop(q);
      printf("consumers get data %c\n",top);
      pthread_mutex_unlock(&mutex);
    }
  }
}

void* producers(void* arg)
{
  SeqQueue* q = (SeqQueue*)arg;
  char i = 'a';

  while(1) {
    pthread_mutex_lock(&mutex);
    SeqQueuePush(q, i);
    printf("producers set data %c\n",i);
    i++;
    sleep(1);
   //pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
  }
}

int main()
{
  pthread_t tid1,tid2;
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&cond, NULL);
  SeqQueue q;
  SeqQueueInit(&q);

  pthread_create(&tid1, NULL, consumers, (void*)&q);
  pthread_create(&tid2, NULL, producers, (void*)&q);

  pthread_join(tid1,NULL);
  pthread_join(tid2,NULL);
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);

  return 0;
}

这里写图片描述

这里写图片描述
这里写图片描述
当我们取消掉条件变量后,发现这个时候结果大相径庭。这时候就体会到了条件变量的作用。


欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值