Linux线程互斥

4 篇文章 0 订阅

一、相关概念

1.临界资源:多线程执行流共享的资源
临界区:每个线程内部,访问临界资源的代码
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完

二、互斥

1.抢票例子
变量–:不是原子操作(一搬是三个步骤,存入,–,输出读取)

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

int ticket = 1000;
pthread_mutex_t lock;  //定义互斥锁

void *TicketGrabbing(void *arg)
{
  const char *name = (char*)arg;
  while(1)
  {

  pthread_mutex_lock(&lock);  //加锁
  //可让一人抢票时,别人不可进入抢票
    if(ticket > 0)
    {
      usleep(100);
      printf("[%s] get a ticket:%d\n",name,ticket--);
      pthread_mutex_unlock(&lock);
    }
    else{

      pthread_mutex_unlock(&lock);
      break;
    }
  }
  /*
  printf("%s quit!\n",name);
  pthread_exit((void*)0);
  */
 // pthread_mutex_unlock(&lock); //解锁
  
}
int main()
{
/*
  pthread_t t1,t2,t3,t4;
  pthread_create(&t1,NULL,TicketGrabbing,"thread 1");

  pthread_create(&t2,NULL,TicketGrabbing,"thread 2");
  pthread_create(&t3,NULL,TicketGrabbing,"thread 3");
  pthread_create(&t4,NULL,TicketGrabbing,"thread 4");

  pthread_join(t1,NULL);
  pthread_join(t2,NULL);
  pthread_join(t3,NULL);
  pthread_join(t4,NULL);
*/
  //以上代码转换
  pthread_t thds[5];
  pthread_mutex_init(&lock,NULL);

  for(int i = 0; i < 5; i++)
  {
    pthread_create(&thds[i],NULL,TicketGrabbing,(void*)i);
  }

  for(int i = 0; i < 5; i++)
  {
    pthread_join(thds[i],NULL);
  }

  pthread_mutex_destroy(&lock);
  return 0;
}

2.互斥量:本质上是一把锁
(1)代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
(2)如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
(3)如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

3.加锁:
(1)大部分情况下,加锁本身是有损于性能的,不可避免的,尽可能减少加锁带来的性能开销
(2)进行临界资源的保护,是所有执行流都应该遵守的标准,程序员编码应注意
(3)原子性体现:
a.没有申请锁
b.锁已经释放
(4)线程1在临界区内是否可以进行线程切换?
可能,即使当前线程被切走,其它线程也无法进入临界区进行资源访问,拿着锁被切走的,没有释放。
4.解锁:

(1)锁本身就是临界资源,锁存在是为了保护临界资源,锁是否需要被保护?
申请锁的过程,必须是原子的!

(2)锁的原子性是如何实现的?lock与unlock过程是怎样的?
把共享的mutex通过exchange方案,原子性的交换到线程自己的上下文当中。

(3)CPU内的寄存器,不是所有的线程共享的!!线程会有切换,但是内存中的数据是共享的

(4)每个线程都可以进行交换,但必须有顺序,指令周期决定!

三、线程安全

1.可重入VS线程安全

线程安全:多个线程并发同一段代码时,不会出现不同的结果。(线程执行代码的是否安全情况)
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其它的执行流再次进入,成为重入。

2.常见的线程不安全的情况

(1)不保护共享变量的函数
(2)函数状态随着被调用,状态发生变化的函数
(3)返回指向静态变量指针的函数
(4)调用线程不安全函数的函数

3. 常见的线程安全的情况

(1)每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
(2)类或者接口对于线程来说都是原子操作
(3)多个线程之间的切换不会导致该接口的执行结果存在二义性

4.常见不可重入的情况

(1)调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
(2)调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
(3)可重入函数体内使用了静态的数据结构

5.常见可重入的情况

(1)不使用全局变量或静态变量
(2)不使用用malloc或者new开辟出的空间
(3)不调用不可重入函数
(4)不返回静态或全局数据,所有数据都有函数的调用者提供
(5)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

6.可重入与线程安全联系

(1)函数是可重入的,那就是线程安全的
(2)函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
(3)如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

7.可重入与线程安全区别

(1)可重入函数是线程安全函数的一种
(2)线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
(3)如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

四、常见锁

锁:即等待队列
死锁:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程s锁站用不会释放的资源而处于的一种永久等待状态。

1.死锁四个必要条件

(1)互斥条件:一个资源每次只能被一个执行流使用
(2)请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
(3)不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
(4)循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

2.避免死锁

(1)破坏死锁的四个必要条件
(2)加锁顺序一致
(3)避免锁未释放的场景
(4)资源一次性分配

3.避免死锁算法

(1)死锁检测算法
(2)银行家算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值