引言
今天刚好上操作系统的实验课,又刚刚好遇到进程的同步与互斥问题。问题如下:
(1) 设计一个模拟若干售票网点的售票程序,并设计多个后台售票线程并发运行。
大家可以先思考如何解决这种问题。结合最近学的Go语言,实验于是就用协程实现。
正文
线程是对进程的进一步细化,而协程就是对线程的进一步细化。详情如下图所示:
进程与线程的关系
线程与协程的关系
线程处于内核态,每个线程可以跑多个协程,栈MB级别;而协程处于用户态,轻量级线程,栈KB级别。
在接下来的问题中,我们边思考边介绍相关知识点······
先来处理第一个问题,首先我们要考虑有几个主体以及几种事件的操作。 主体:首先买票肯定需要票的数量以及其状态(是否被卖出),这是最基本的;然后买票的窗口。 事件:多个用户在不同窗口同时买一张票(但是只有一个用户可以买到) 接下来我们可以对主题的属性进行结构化,使用Go语言中的结构体和常量的声明。代码如下:
const (
NumTickets = 100 // 总票数
NumWindows = 5 // 售票窗口数
)
type ticket struct {
num int // 票号
sold bool // 是否已售出
mutex sync.Mutex // 互斥锁
}
这就完成对基本所需的值的定义。然后接下来进行互斥操作,就是在同一时刻只允许一个用户对同一张票进行操作。这里我们需要在票的属性中加入互斥锁,也就是所谓的信号量。当有一个用户对当前的票进行操作时,不允许其他用户再对这张票进行操作。这部分的代码实现细节如下:
// 随机选择一张未售出的票
var t *ticket
for _, ticket := range tickets {
ticket.mutex.Lock()
if !ticket.sold {
t = ticket
ticket.mutex.Unlock()
break
}
ticket.mutex.Unlock()
}
// 如果所有票都已售出,则退出售票窗口
if t == nil {
break
}
// 售出票
t.sold = true
time.Sleep(40 * time.Microsecond)
fmt.Printf("窗口%d售出票%d\n", id, t.num)
从万千票中拿出一个,ticket.mutex.Lock()就是将你和这张票一起关起来,只有你接触得到这张票,而其他人不能对着张票进行任何操作。只有当你不要时,也就是ticket.mutex.Unlock(),这张票才会重新放回票池。
当让如何这只解决了如何防止多个人同时买一张票,那么如何让多个人同时买票呢?这里就需要用到协程,在Go语言中只需要在函数之前加个Go就可以调用协程。具体代码实现如下:
// 创建多个售票窗口
var wg sync.WaitGroup
for i := 1; i <= NumWindows; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for {
// 售票操作
}
// 等待所有售票窗口完成售票
wg.Wait()
fmt.Println("所有票已售出")
Go里面的WaitGroup是非常常见的一种并发控制方式,它可以让我们的代码等待一组goroutine的结束。比如在主协程(main函数)中等待几个子协程(go函数)完成操作,而不是利用time.Sleep()使得主协程让出处理器给子协程。 完整代码如下:
package main
import (
"fmt"
"sync"
"time"
)
const (
NumTickets = 100 // 总票数
NumWindows = 5 // 售票窗口数
)
type ticket struct {
num int // 票号
sold bool // 是否已售出
mutex sync.Mutex // 互斥锁
}
func main() {
// 初始化票池
var tickets []*ticket
for i := 1; i <= NumTickets; i++ {
tickets = append(tickets, &ticket{
num: i,
sold: false,
mutex: sync.Mutex{},
})
}
// 创建多个售票窗口
var wg sync.WaitGroup
for i := 1; i <= NumWindows; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for {
// 随机选择一张未售出的票
var t *ticket
for _, ticket := range tickets {
ticket.mutex.Lock()
if !ticket.sold {
t = ticket
ticket.mutex.Unlock()
break
}
ticket.mutex.Unlock()
}
// 如果所有票都已售出,则退出售票窗口
if t == nil {
break
}
// 售出票
t.sold = true
time.Sleep(40 * time.Microsecond)
fmt.Printf("窗口%d售出票%d\n", id, t.num)
}
}(i)
}
// 等待所有售票窗口完成售票
wg.Wait()
fmt.Println("所有票已售出")
}
Go语言入门小白,如果不当之出,请多多指正!感谢!