二 Linux--多线程( 二 )


#include <stdio.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t mutex;// 创建锁变量//全局变量,所有线程共享int ticket = 10;void* get_tickets(void* arg){ long id = (long)arg; while (1){usleep(1000);// 加锁pthread_mutex_lock(&mutex);if (ticket > 0){// 有票--ticket;printf("线程%ld获得一张票,剩余%d张票\n",id,ticket);// 解锁pthread_mutex_unlock(&mutex);}else{// 无票 , 退出// 解锁pthread_mutex_unlock(&mutex);break;} }}int main(){ pthread_t t[5]; // 初始化锁 pthread_mutex_init(&mutex, NULL); // 创建5个线程 long i = 0; for (; i < 5; ++i) { pthread_create(t+i, NULL, get_tickets, (void*)(i+1)); } // 释放5个线程 for (i = 0; i < 5; ++i) {pthread_join(t[i], NULL); } // 销毁锁 pthread_mutex_destroy(&mutex); return 0;}运行结果如下:

二 Linux--多线程

文章插图
总结几点并回答几个问题:
锁的作用: 对临界区进行保护,所有的执行流线程都必须遵守这个规则:lock——>访问临界区——>unlock
需要注意的点:
  • 所有的线程必须看到同一把锁,锁本身就是临界资源,所以锁本身需要先保证自身安全申请锁的过程不能出现中间态,必须保证原子性
  • 任一线程持有锁之后 , 其它线程如果还想申请锁时申请不到的,保证互斥性
线程申请不到锁此时会做什么?
进入等待队列进行等待 , 从运行队列转移到等待队列,状态由R变成S,持有锁的线程unlock之后,需要唤醒等待队列中的第一个线程
struct mutex{  int lock;// 0 1     // ...     sturct wait_queue;//锁下的等待队列}互斥量的原理大多数体系结构都提供了swap或exchange指令 , 该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期 。下面是lock和unlock的伪代码
lock: movb $0, %a1     # 把0值放进寄存器a1里 xchgb %a1, mutex # 交换a1寄存器的内容和锁的值(无线程使用锁时 , metux的值为1) if (%a1 > 0)return 0; # 得到锁 else挂起等待; goto lock;unlock: movb $1 mutex  #把1赋给锁 唤醒等待的线程; return 0;在上述加锁的伪代码中演示了上步骤:
  1. 对寄存器的内容进行清0
  2. 把mutex的值(被使用值为0,未被使用值为1)和寄存器的内容进行交换
  3. 寄存器的内容为1代表得到了锁,为0代表未得到锁,要挂起等待

二 Linux--多线程

文章插图
解锁的伪代码步骤(只有有锁的线程才可以执行到这段代码):
  1. 把mutex的值改为1
  2. 唤醒等待锁的线程
死锁概念: 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去 。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程 。
举个例子:
二 Linux--多线程

文章插图
这里线程1先申请资源1,申请到了之后,资源1被锁死(资源1会永远被线程1申请,因为只有申请到资源2执行完临界代码,才会释放掉资源1,此时线程1被卡在申请资源2的点,根本走不到释放资源1的代码,所以会一直被线程1占有) , 线程2无法申请,线程2先申请资源2,同样资源2也被锁死,这样当线程1继续向下申请资源2的时候,就被阻塞在那里,线程2在向下申请资源1的时候,也被阻塞在那里,这就形成了死锁,永远解不了锁 。
死锁引起的原因: