Go语言——协程的同步与互斥

232 阅读3分钟

引言

今天刚好上操作系统的实验课,又刚刚好遇到进程的同步与互斥问题。问题如下:

(1)   设计一个模拟若干售票网点的售票程序,并设计多个后台售票线程并发运行

大家可以先思考如何解决这种问题。结合最近学的Go语言,实验于是就用协程实现。

正文

线程是对进程的进一步细化,而协程就是对线程的进一步细化。详情如下图所示:

进程与线程的关系

未命名文件.png

线程与协程的关系

未命名文件 (1).png

线程处于内核态,每个线程可以跑多个协程,栈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语言入门小白,如果不当之出,请多多指正!感谢!