Linux之线程同步与互斥,模拟抢票系统

线程的同步与互斥

相关背景概念
  • 临界区 :每个线程内部访问自己资源的代码叫做临界区:
  • 临界资源 :多个执行流共享的资源叫做临界资源
  • 互斥 :在任何时刻,只能有且仅有一个执行流访问临界区的临界资源
  • 原子性 :一个操作不会被任何调度机制打断,要么完成,要么没完成,只有两种状态,常见的++,–都不是原子操作,因为汇编代码不止一条
互斥量mutex
  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

我们模拟实现抢票系统说明:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;

int tickets = 100; //临界资源

void* route(void* arg)
{
  char* id = (char*)arg;
  while(1)
  {
    if(tickets>0)
    {
      usleep(10000);
      fflush(stdout);
      cout<< id <<" get a ticket,now, tickets is:  "<< tickets <<endl;
      tickets--;
    }
    else{
      cout<<"tickets is full"<<endl;
      break;
    }
  }
  return (void*)0;
}
int main()
{
  pthread_t t1, t2, t3, t4;

  pthread_create(&t1, NULL, route, "thread 1");
  pthread_create(&t2, NULL, route, "thread 2");
  pthread_create(&t3, NULL, route, "thread 3");
  pthread_create(&t4, NULL, route, "thread 4");

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);
  return 0;
}

很显然,这个代码出问题了,当我们的票数到了 0 时就应该退出停止抢票,结果却不是这样。
为什么会这样呢?

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • ticket-- 操作本身就不是一个原子操作

那么,我们要怎么样改正呢?做到下面三点

  • 代码必须有互斥行为,当代码进入临界区获取资源时,不允许其他线程进入临界区
  • 当有多个线程同时来执行临界区代码时,并且此时临界区没有线程正在执行,那么允许且仅允许一个线程进入临界区
  • 如果该线程不在临界区执行,那么不能阻塞其他线程进入临界区

其实本质上就是加一把锁,Linux下叫做 互斥锁。

互斥量接口

初始化互斥量

有两种方式

  • 方法1,静态分配:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 方法2,动态分配:
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t* restrict attr);
    参数:
    mutex:要初始化的互斥量
    attr:NULL
销毁互斥量

注意事项

  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量的加锁和解锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回 0,失败返回错误码。

使用pthread_lock注意:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

说到这里,我们将上面的抢票在稍加修改:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <vector>

using namespace std;

volatile int tickets = 100;   //临界资源,需要加锁,否则多个线程访问临界资源会发生问题
pthread_mutex_t lock;  //互斥锁

void* BuyTickets(void* arg)
{
  int* id=(int*)arg;
  int number = *id;
  while(1)
  {
    usleep(100);
    pthread_mutex_lock(&lock);
    if(tickets > 0)
    {
      usleep(10000);
      cout<<" thread "<< number << " get a ticket: "<< tickets <<endl;
      tickets--;
      pthread_mutex_unlock(&lock);
    }
    else{
      pthread_mutex_unlock(&lock);
      cout<<"tickets is full..."<<endl;
      return (void*)0;
    }
  }
  return (void*)0;
}
struct attr{
  int _num;
  pthread_t tid;
};
int main()
{
  int num = 5;
  vector<attr> thread_list(num);
  pthread_mutex_init(&lock,NULL);

  for(int i=0;i<num;++i)
  {
    pthread_create(&(thread_list[i].tid),NULL,BuyTickets,(void*)&i);
    thread_list[i]._num = i;
  }
  for(int i=0;i<num;++i)
  {
    pthread_join(thread_list[i].tid,NULL);
  }
  pthread_mutex_destroy(&lock);
  return  0;
}

大功告成。
这里我说几个我在写代码中遇到的问题:

pthread_create(&(thread_list[i].tid),NULL,BuyTickets,(void*)&i);

  • 在写这个线程创建函数的时候,最后一个参数一定要传地址,否则会有警告,因为这里我们的 i 是一个常数,但函数原型传的是 (void *)类型的

  • 注意for(int i=0;i<num;++i) 这里的 i 是根据我们定义的抢票人数来创建线程的,如果写的不对会造成 core dump。我就在这里卡了很久

全部评论

相关推荐

好像有点准
我推的MK:感觉这个表格呢好像有用又好像没用,真有offer了不管加班多么严重也得受着,没offer管他加班什么样也只能看看,反正轮不到我选
点赞 评论 收藏
分享
爱吃肉的伊登在写日记:好棒,27届简历能做成这个样子,但是第一个项目感觉cover住难度还是不小的,特别是二面的时候肯定要对分布式系统设计这一块儿有高出正常面试者的水平才行
点赞 评论 收藏
分享
04-13 18:10
门头沟学院 Java
想熬夜的小飞象在秋招:被腾讯挂了后爸妈以为我失联了
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务