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

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

互斥关系

线程之间的互斥关系


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

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

#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;
}

执行结果:
执行结果

猜你喜欢

转载自blog.csdn.net/misszhoudandan/article/details/80739734