欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程的基本概念(三)同步与互斥关系、什么是死锁

程序员文章站 2022-05-22 11:21:39
...

在前面两篇中介绍了线程的基本概念和线程控制
今天来看一下线程之间的同步和互斥关系

互斥关系

线程之间的互斥关系


对于一块临界资源,同一时间只能有一个线程进行访问,对于之前学习的进程间通信中讲的管道和消息队列,均内置的互斥同步机制。

大部分情况下,线程使用的函数都是全局的,如果这样的话,就可能发生当一个线程正在访问一资源时,另外一个线程也来访问该资源,此时就可能发生逻辑错误。经典场景即使售票机制。

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


//************购票问题

int ticket=10;
pthread_mutex_t mutex;

void * Entry(void * arg)
{
  (void)arg;
  while(1)
  {
    if(ticket>0)//如果还有票。就执行买票,票数减一
    {
      ticket--;
      printf("ticket:%d\n",ticket);
    }
    else//没有票就退出
    {
      break;
    }
    sleep(1);
  }
  return NULL;
}
void test()
{
  pthread_t thread[10];
  //创建线程
  int i=0;
  for(i=0;i<10;i++)
  {
    pthread_create(&thread[i],NULL,Entry,NULL);
  }
  //进行线程等待
  for(i=0;i<10;i++)
  {
    pthread_join(thread[i],NULL);

  }
}

int main()
{
  test();
  return 0;

}

利用互斥量来解决上面的问题,保证每次判断票数不为0 和票数减一这两个操作为原子操作。即对访问临临界资源的那段代码进行上锁。

mutex 互斥量

基于cpu中实现了将寄存器中的值和内存中的值交换的原子操作

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

//进程间互斥关系
//购票问题

int ticket=10;
pthread_mutex_t mutex;

void * Entry(void * arg)
{
  (void)arg;
  while(1)
  {
    //获取互斥锁,保证下面的操作一次全部执行完(原子操作)
    pthread_mutex_lock(&mutex);
    if(ticket>0)
    {
      ticket--;
      printf("ticket:%d\n",ticket);
    }
    else
    {
      break;
    }
    pthread_mutex_unlock(&mutex);
    //释放互斥锁,保证其他线程可以进行访问
    sleep(1);
  }
  return NULL;
}

void test()
{
  pthread_t thread_1,thread_2;

  //初始化互斥量
  pthread_mutex_init(&mutex,NULL);
  //创建两个线程
  pthread_create(&thread_1,NULL,Entry,NULL);
  pthread_create(&thread_2,NULL,Entry,NULL);
  //进行线程等待
  pthread_join(thread_1,NULL);
  pthread_join(thread_2,NULL);
  //销毁互斥量
  pthread_mutex_destroy(&mutex);
}

int main()
{
  test();
  return 0;
}

线程的基本概念(三)同步与互斥关系、什么是死锁

同步关系

线程之间的同步关系


为了完成同以目标,需要线程之间按照一定是顺序来执行,不仅是为了保证正确性,也是为了提高效率。

条件变量

在linux 操作系统下实现了用条件变量实现进程间同步关系
这里用球员之间传球和投篮之间的同步关系来说明

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


//************简单同步问题***********************
//投篮和传球·

pthread_cond_t g_cond;//条件变量
pthread_mutex_t mutex;//互斥量

void * Pass(void * arg)
{
  (void )arg;
  while(1)
  {
    pthread_cond_wait(&g_cond,&mutex);//等待投篮的人
    printf("传球\n");
    sleep(1);
  }
  return NULL;
}

void * Shoot(void * arg)
{
  (void )arg;
  while(1)
  {
    printf("投篮\n");
    sleep(1);
    pthread_cond_signal(&g_cond);//告知传球的人可以进行传球了
    sleep(2);
  }
  return NULL;
}


void test()
{
  //初始化互斥量
  pthread_mutex_init(&mutex,NULL);
  //初始化条件变量
  pthread_cond_init(&g_cond,NULL);

  pthread_t thread_1,thread_2;
  //创建两个线程,分别完成传球和投篮任务
  pthread_create(&thread_1,NULL,Shoot,NULL);
  pthread_create(&thread_2,NULL,Pass,NULL);

  //进行线程等待
  pthread_join(thread_1,NULL);
  pthread_join(thread_2,NULL);
  //销毁互斥量
  pthread_mutex_destroy(&mutex);
  //销毁条件变量
  pthread_cond_destroy(&g_cond);
}


int main()
{
  test();
  return 0;
}

若想更深入了了解同步互斥问题
可以参考一下:
经典互斥问题—生产者消费者模型
经典同步问题—读者写着模型

什么是死锁

在我们解决互斥问题时,我们会在临界区加上锁,那么就会存在这样的问题,当一个线程已经获取了锁,还没有进行释放该锁,又尝试获取再次获取锁,很明显这把锁已经被自己占用了,还没有来的及释放,再次获取锁时一定会阻塞,直到等到锁,那么,既不能释放拥有的锁,也不可获得当前的锁,该线程就会一直阻塞,我们称类似于这种状态为死锁状态。
造成死锁的原因

进程没有及时的释放锁
1. 一个进程尝试获取两次锁
2. 尝试交叉式获取锁,n个进程n把锁,都尝试获取对方的锁(哲学家就餐问题)

线程安全函数

线程安全函数:多个线程调用该函数不会出现任何逻辑错误

之前我们讲过,可重入函数可重入函数
可重入函数:在不同的执行流中调用该函数不会出现逻辑错误
这里的不同执行流,不仅包含线程,还包含信号处理函数,所以可重入函数要求更严格
可重入函数一定是线程安全函数
线程安全函数不一定是可重入的
一个线程安全但不可重入的例子

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

//定义一个全局的互斥量
pthread_mutex_t mutex;
//定义一个全局的变量
int count = 5;



//下面的函数是线程安全函数,但不是可重入函数
void pthread_security_function()
{
  while(1)
  {
    //上锁
    pthread_mutex_lock(&mutex);
    if(count < 0)
    {
      exit(1);
    }
    sleep(3);
    printf("count: %d\n",count);
    sleep(1);
    count--;
    //解锁
    pthread_mutex_unlock(&mutex);
  }
}


//线程入口函数
void * Entry(void * arg)
{
  (void)arg;
  pthread_security_function();
  return NULL;
}


//信号处理函数
void sig_entry(int sig)
{
  //在信号处理函数中进行调用一个线程安全函数
  (void)sig; 
  pthread_security_function();
}



void test()
{
  //对互斥量进行初始化
  pthread_mutex_init(&mutex,NULL);

  //信号捕捉
  signal(SIGINT,sig_entry);

  //创建线程
  pthread_t tid_1,tid_2;
  pthread_create(&tid_1,NULL,Entry,NULL);
  pthread_create(&tid_2,NULL,Entry,NULL);

  //线程等待
  pthread_join(tid_1,NULL);
  pthread_join(tid_2,NULL);

  //销毁互斥量
  pthread_mutex_destroy(&mutex);


}


int main()
{
  test();
  return 0;
}

执行结果:
线程的基本概念(三)同步与互斥关系、什么是死锁