并发
首先来了解一下,什么是并发进程/线程
在内存中同时存在的若干个进程/线程,由操作系统的调度程序采用适当的策略将他们调度到CPU上运行,同时维护队列
- 宏观上,多个并发的进程/线程看起来是同时运行
- 微观上,它们是交替运行(Interleaving)
由于进程/线程切换速度很快,看起来好像是"同时"运行。
并发进程之间的关系
并发进程之间有独立关系和交互关系
如果是交互关系,则又可分为竞争(Race)和协作关系(Cooperation)
竞争
竞争关系就好比两条分路的车(线程)争夺同一个匝道(资源),道路上只能有一辆车运行,另外一辆车需要等待(waiting).
协作
协作关系可以理解为,匝道上每过两辆车,此时才允许分路上的一辆车进入匝道,在此期间分路上的车需要等待(waiting).
产生的一些问题
以竞争关系为例,如果两辆车同时进入了匝道,相当于多个线程并发操作了同一个资源
给出一个经典的订票问题
T = x; // x表示剩余票数
if(T >= 1) {
x = T - 1;
}
假设剩余两张票(x=2),如果两个线程(T1, T2)同时进入了订票系统,并且它们交替执行
T1
线程执行 T = x,此时T = 2- 进程切换,
T2
线程执行T = x,此时T = 2 T2
线程进行if
语句判断,x
被赋值为1- 进程切换,
T1
线程进行if
语句判断,x
被赋值为1
对于现实生活,两个人同时在买票平台上购买了一张票。可是最后平台的余票只减少了一张!这显然不合理。
所以我们需要一种机制,来解决这种情况。
同步
首先来看一段定义
Process Synchronization means a mechanism to maintain the consistency of data shared in cooperative processes.
意思是,同步机制指维护协作的进程之间的共享数据一致性
常用的两种同步工具有互斥锁和信号量, 本文暂且介绍互斥锁
互斥锁
Mutex Locks: Operating-System designers build software tools to solve the critical-section problem. The simplest of these tools is the mutex lock.
意味着,互斥锁是解决临界区问题的工具,同时它的使用也较为简单
那么,什么是临界区呢?
临界区
每个并发进程可能都有一段代码块,这段代码块中,进程可能会改变一些共有的变量。这段代码块被称之为临界区(Critical-Section)。
该系统的重要特点是,当一个进程在临界区执行时,其它进程不能执行。
对于临界区问题,我们需要设计一种使得各进程之间可以协作的协议(protocol)
进程进出临界区协议 ![critical-section protocol](https://i-blog.csdnimg.cn/blog_migrate/365289ebe0f4712e06ae78c32a45a246.png)
对于临界区的管理,我们有三个准则
- Mutual exclusion(Mutex): 互斥,当一个进程在临界区执行时,其它进程不能执行。
- Progress: 当一个线程运行完毕(使用完资源)后,必须开放这块资源的使用权。让别的线程可以使用资源
- Bounded waiting: 不能让正在等待的线程等待太久
互斥锁解决订票问题
基本思路是,我们需要在使用临界区前,对这块区域上锁(Lock)
使用完临界区后,对这块区域解锁(Unlock)
这也就意味着,当一个线程在买票时,另一个线程不能干预。
实现代码(C):
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define THREAD_NUM 2
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 定义一把锁
int ticketAmount = 2; // 余票
void *ticketAgent(void *arg)
{
pthread_mutex_lock(&lock); // acquire lock
// -----临界区-----
int t = ticketAmount;
if (t > 0)
{
printf("One ticket sold!\n");
t--;
}
else
{
printf("Ticket sold out!!\n");
}
ticketAmount = t;
// -----临界区-----
pthread_mutex_unlock(&lock); // release lock
pthread_exit(0); // 线程结束
}
int main(int argc, char const *argv[])
{
pthread_t ticketAgent_tid[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
{
pthread_create(ticketAgent_tid + i, NULL, ticketAgent, NULL); // 线程创建函数
}
for (int i = 0; i < THREAD_NUM; i++)
{
pthread_join(ticketAgent_tid[i], NULL);
}
printf("The left ticket is %d\n", ticketAmount);
return 0;
}