这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
go并发
go的并发是基于协程。协程是基于用户线程的。我们将线程认为的切分为多个部分。通过线程中我们自己定义的调度方式来调用我们划分的模块,这就为协程。相较于线程的切换。我们的协程的切换代价更小。保存协程信息所占用的空间更小。所以,go原生就支持高并发。
以下是一个简单的并发的例子:
var wg sync.WaitGroup
func coroutine(i int) {
fmt.Println(i)
wg.Done()
}
func main() {
wg.Add(100)
for i := 0; i < 100; i++ {
go coroutine(rand.Intn(100))
}
wg.Wait()
}
在并发中,我们需要考虑到的一个问题就是不同流程中的通信。我们在操作系统中学习了mutex互斥锁与临界区的概念。而在go的标准库sync中,就包含了这一套互斥锁。
var (
x int
lock sync.Mutex
wg sync.WaitGroup
)
func coroutine() {
lock.Lock()
x += 1
lock.Unlock()
wg.Done()
}
func main() {
wg.Add(10000)
for i := 0; i < 10000; i++ {
go coroutine()
}
wg.Wait()
fmt.Println(x)
}
进入临界区是加锁,离开临界区是解锁。我们期望的结果是10000。如果不进行加锁操作的话,由于多个流程同时读取数据的问题,会造成脏读。从而使得结果不正确。
进程通信还有一种管道的模式。go使用这种模式来进行由通信到共享内存。而不是由共享内存到通信。chan对象,在go中使用chan关键。
func main() {
src := make(chan int, 2)
dest := make(chan int, 2)
go func() {
defer close(src)
src <- rand.Intn(100)
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * 2
}
}()
for i := range dest {
fmt.Println(i)
}
}
这里的"<-"就相当于cpp的流运算。我们将数据送入到channel中。
项目开发
总体上分为三个部分。第一个部分是项目的拆解。也就是需求分析。这方面又会划分的更加细致。
需求分析
在需求分析中,我们需要详细的描述出对方的需求。这样才能直到我们的代码需要什么样的架构与逻辑。
我们需要在需求分析中,抽出相关的实体的模型。从而将实体抽象化,得到一个逻辑上的,底层可以存储的对象模型。
根据需求用例进行用户分析得到尽可能的完善的业务逻辑。我们可以使用ER图来帮助我们划分实体之间的关系。如果使用的是关系型数据库的话。ER的化简还可以来帮助我们进行数据模型的设计。
代码设计
从实体模型出发,分层结构:
- 数据层。数据模型,外部数据的增删改查。
例如我们的用户数据的存储,与读取,就是需要在数据层解决的问题。
- 逻辑层, 业务实体,处理核心业务逻辑输出
在逻辑层也就是我们具体的服务代码实现。例如我们的服务提供一个查找不同时间段登录用户数据。那么就需要在此层进行编写。而关于数据的查询相关操作则是调用数据层提供的接口即可。
- 视图层, 试图, 处理和外部的交互逻辑
视图层则是提供给客户端的接口API层。我们的整体所有能够提供的服务都由此入口进入。
这三层从下到上一次从client到service。
测试运行
为了保证我们的代码正确可靠。我们需要进行测试。测试分为回归测试,集成测试与单元测试。
回归测试则是模拟正常用户的操作逻辑与行为对程序进行测试。
集成测试是针对代码暴露出来的API接口进行测试。
而单元测试则是以函数为基本单元进行期望的测试。
程序的正常运行离不开测试。这点很重要。