Golang代码编程规范

zkbhj 发表了文章 • 0 个评论 • 1350 次浏览 • 2020-04-01 17:36 • 来自相关话题

1、在 Go 代码上运行 gofmt 以自动修复大多数的机械性风格问题;
2、注释文档声明应该是完整的句子,以所描述事物的名称开头,并以句点结束;
3、Context 都是不可变的,因此可以将相同的 ctx 传递给多个共享相同截止日期,取消信号,安全凭据,跟踪等的调用;
4、如果 T 类型的方法与其指针类型 *T 相关联,请不要复制 T 类型的值;
5、不要使用包math/rand来生成密钥,请使用crypto/rand的 Reader 作为替代;
6、当声明一个空 slice 时,倾向于用var t string,nil slice 是首选的风格;
7、所有的顶级导出的名称都应该有 doc 注释,重要的未导出类型或函数声明也应如此;
8、不要将 panic 用于正常的错误处理。使用 error 和多返回值;
9、错误信息字符串不应大写(除非以专有名词或首字母缩略词开头)或以标点符号结尾;
10、添加新包时,请包含预期用法的示例:可运行的示例,或是演示完整调用链的简单测试;
11、当你生成 goroutines 时,要清楚它们何时或是否会退出(了解生命周期),请尽量让并发代码足够简单,从而更容易地确认 goroutine 的生命周期。否则容易产生内存泄漏等不可预知的问题;
12、不要使用 _ 变量丢弃 error。如果函数返回 error,请检查它以确保函数成功;
13、避免包重命名导入,防止名称冲突;好的包名称不需要重命名。如果发生命名冲突,则更倾向于重命名最接近本地的包或特定于项目的包。包导入按组进行组织,组与组之间有空行。标准库包始终位于第一组中;
14、除了特殊情况,不要在程序中使用 import .;
15、In-Band Errors,函数应返回一个附加值以指示其他返回值是否有效;
16、尝试将正常的代码路径保持在最小的缩进处,优先处理错误并缩进;
17、名称中的单词是首字母或首字母缩略词(例如 “URL” 或 “NATO” )需要具有相同的大小写规则,如如 “urlPony” 或 “URLPony”,而不是 “Url”;
18、Go 接口通常属于使用 interface 类型值的包,而不是实现这些值的包;
19、减少不必要的换行,如果行太长,可以更改名称或者语义,可能会起到很好的结果;类似的,如果函数太大,可能需要改变这个函数的功能边界,从而达到减小的目的;
20、混合大小写规则,导出常量大写字母开头,否则小写;
21、如果函数返回两个或三个相同类型的参数,那么在某些上下文中添加命名可能很有用,文档的清晰度总比在函数中的一行两行更重要;
22、包注释必须出现在 package 声明的临近位置,无空行;包注释的首字母必须大写;
23、包中名称的所有引用都将使用包名完成,因此您可以从标识符中省略该名称;
24、非必要是不必使用指针传递,使用值传递即可,除非是大型 struct类型或者是可能生长的小型 struct;
25、方法接收者的名称应该反映其身份;通常,其类型的一个或两个字母缩写就足够了,切使用上要保持一致,如果你在一个方法中叫将接收器命名为“c”,那么在其他方法中不要把它命名为“cl”;
26、在函数传参时,是使用值接收器还是使用指针接收器?几个标准和原则如下:
如果接收器是 map,func或 chan,则不要使用指向它们的指针。如果接收器是 slice 并且该方法不重新切片或不重新分配切片,则不要使用指向它的指针。如果该方法需要改变接收器的值,则接收器必须是指针。如果接收器是包含 sync.Mutex 或类似同步字段的 struct,则接收器必须是避免复制的指针。如果接收器是大型结构或数组,则指针接收器更有效。多大才算大?假设它相当于将其包含的所有元素作为参数传递给方法。如果感觉太大,那么对接收器来说也太大了。函数或方法可以改变接收器吗(并发调用或调用某方法时继续调用相关方法或函数)?在调用方法时,值类型会创建接收器的副本,因此外部更新将不会应用于此接收器。如果必须在原始接收器中看到更改效果,则接收器必须是指针。如果接收器是 struct,数组或 slice,并且其任何元素是指向可能改变的对象的指针,则更倾向于使用指针接收器,因为它将使读者更清楚地意图。如果接收器是一个小型数组或 struct,那么它自然是一个值类型(例如,类似于time.Time类型),对于没有可变字段,没有指针的类型,或者只是一个简单的基本类型,如 int 或 string,值接收器是合适的。值接收器可以减少可以生成的垃圾量;如果将值作为参数传递给值类型方法,则可以使用堆栈上的副本而不需要在堆上进行分配。(编译器试图避免这种分配,但它不能总是成功)因此,在没有进行分析之前,不要选择值接收器类型。最后,如有疑问,请使用指针接收器。
27、相比异步函数更倾向于同步函数——直接返回结果的函数,或是在返回之前已完成所有回调或 channel 操作的函数。尽量减少异步函数的使用。同步函数让 goroutine 在调用中本地化,能够更容易地推断其生命周期并避免泄漏和数据竞争;
28、在任何情况下,你都有责任向可能会在将来调试你的代码的开发者提供有用的消息;
29、Go 中的变量名称应该短而不是长,尤其是范围域内的局部变量。基本规则:范围域中,越晚使用的变量,名称必须越具有描述性。对于方法接收器,一个或两个字母就足够了。
 
原文链接:https://github.com/golang/go/w ... ments 查看全部
1、在 Go 代码上运行 gofmt 以自动修复大多数的机械性风格问题;
2、注释文档声明应该是完整的句子,以所描述事物的名称开头,并以句点结束;
3、Context 都是不可变的,因此可以将相同的 ctx 传递给多个共享相同截止日期,取消信号,安全凭据,跟踪等的调用;
4、如果 T 类型的方法与其指针类型 *T 相关联,请不要复制 T 类型的值;
5、不要使用包math/rand来生成密钥,请使用crypto/rand的 Reader 作为替代;
6、当声明一个空 slice 时,倾向于用var t string,nil slice 是首选的风格;
7、所有的顶级导出的名称都应该有 doc 注释,重要的未导出类型或函数声明也应如此;
8、不要将 panic 用于正常的错误处理。使用 error 和多返回值;
9、错误信息字符串不应大写(除非以专有名词或首字母缩略词开头)或以标点符号结尾;
10、添加新包时,请包含预期用法的示例:可运行的示例,或是演示完整调用链的简单测试;
11、当你生成 goroutines 时,要清楚它们何时或是否会退出(了解生命周期),请尽量让并发代码足够简单,从而更容易地确认 goroutine 的生命周期。否则容易产生内存泄漏等不可预知的问题;
12、不要使用 _ 变量丢弃 error。如果函数返回 error,请检查它以确保函数成功;
13、避免包重命名导入,防止名称冲突;好的包名称不需要重命名。如果发生命名冲突,则更倾向于重命名最接近本地的包或特定于项目的包。包导入按组进行组织,组与组之间有空行。标准库包始终位于第一组中;
14、除了特殊情况,不要在程序中使用 import .;
15、In-Band Errors,函数应返回一个附加值以指示其他返回值是否有效;
16、尝试将正常的代码路径保持在最小的缩进处,优先处理错误并缩进;
17、名称中的单词是首字母或首字母缩略词(例如 “URL” 或 “NATO” )需要具有相同的大小写规则,如如 “urlPony” 或 “URLPony”,而不是 “Url”;
18、Go 接口通常属于使用 interface 类型值的包,而不是实现这些值的包;
19、减少不必要的换行,如果行太长,可以更改名称或者语义,可能会起到很好的结果;类似的,如果函数太大,可能需要改变这个函数的功能边界,从而达到减小的目的;
20、混合大小写规则,导出常量大写字母开头,否则小写;
21、如果函数返回两个或三个相同类型的参数,那么在某些上下文中添加命名可能很有用,文档的清晰度总比在函数中的一行两行更重要;
22、包注释必须出现在 package 声明的临近位置,无空行;包注释的首字母必须大写;
23、包中名称的所有引用都将使用包名完成,因此您可以从标识符中省略该名称;
24、非必要是不必使用指针传递,使用值传递即可,除非是大型 struct类型或者是可能生长的小型 struct;
25、方法接收者的名称应该反映其身份;通常,其类型的一个或两个字母缩写就足够了,切使用上要保持一致,如果你在一个方法中叫将接收器命名为“c”,那么在其他方法中不要把它命名为“cl”;
26、在函数传参时,是使用值接收器还是使用指针接收器?几个标准和原则如下:
  • 如果接收器是 map,func或 chan,则不要使用指向它们的指针。如果接收器是 slice 并且该方法不重新切片或不重新分配切片,则不要使用指向它的指针。
  • 如果该方法需要改变接收器的值,则接收器必须是指针。
  • 如果接收器是包含 sync.Mutex 或类似同步字段的 struct,则接收器必须是避免复制的指针。
  • 如果接收器是大型结构或数组,则指针接收器更有效。多大才算大?假设它相当于将其包含的所有元素作为参数传递给方法。如果感觉太大,那么对接收器来说也太大了。
  • 函数或方法可以改变接收器吗(并发调用或调用某方法时继续调用相关方法或函数)?在调用方法时,值类型会创建接收器的副本,因此外部更新将不会应用于此接收器。如果必须在原始接收器中看到更改效果,则接收器必须是指针。
  • 如果接收器是 struct,数组或 slice,并且其任何元素是指向可能改变的对象的指针,则更倾向于使用指针接收器,因为它将使读者更清楚地意图。
  • 如果接收器是一个小型数组或 struct,那么它自然是一个值类型(例如,类似于time.Time类型),对于没有可变字段,没有指针的类型,或者只是一个简单的基本类型,如 int 或 string,值接收器是合适的。值接收器可以减少可以生成的垃圾量;如果将值作为参数传递给值类型方法,则可以使用堆栈上的副本而不需要在堆上进行分配。(编译器试图避免这种分配,但它不能总是成功)因此,在没有进行分析之前,不要选择值接收器类型。
  • 最后,如有疑问,请使用指针接收器。

27、相比异步函数更倾向于同步函数——直接返回结果的函数,或是在返回之前已完成所有回调或 channel 操作的函数。尽量减少异步函数的使用。同步函数让 goroutine 在调用中本地化,能够更容易地推断其生命周期并避免泄漏和数据竞争;
28、在任何情况下,你都有责任向可能会在将来调试你的代码的开发者提供有用的消息;
29、Go 中的变量名称应该短而不是长,尤其是范围域内的局部变量。基本规则:范围域中,越晚使用的变量,名称必须越具有描述性。对于方法接收器,一个或两个字母就足够了。
 
原文链接:https://github.com/golang/go/w ... ments

Golang学习:Go goroutine理解

zkbhj 发表了文章 • 0 个评论 • 1820 次浏览 • 2019-12-19 16:10 • 来自相关话题

Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

为了更好理解Goroutine,现讲一下线程和协程的概念

线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。

协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。

和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。

Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。package main

import "fmt"

func main() {

messages := make(chan string)

go func() { messages <- "ping" }()

msg := <-messages
fmt.Println(msg)
}
先给个简单实例
func loop() {
for i := 0; i < ; i++ {
fmt.Printf("%d ", i)
}
}

func main() {
go loop() // 启动一个goroutine
loop()
}GO并发的实现原理

一、Go并发模型

Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。

CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。

请记住下面这句话:
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”

普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。

Go的CSP并发模型,是通过goroutine和channel来实现的。

goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

生成一个goroutine的方式非常的简单:Go一下,就生成了。
go f();通信机制channel也很方便,传数据用channel <- data,取数据用<-channel。

在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

示例如下:
package main

import "fmt"

func main() {

messages := make(chan string)

go func() { messages <- "ping" }()

msg := <-messages
fmt.Println(msg)
}注意 main()本身也是运行了一个goroutine。

messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道

chan 是关键字 代表我要创建一个通道。
 
GO并发模型的实现原理

我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。

我们现在的计算机语言,可以狭义的认为是一种“软件”,它们中所谓的“线程”,往往是用户态的线程,和操作系统本身内核态的线程(简称KSE),还是有区别的。

线程模型的实现,可以分为以下几种方式:

用户级线程模型





 
如图所示,多个用户态的线程对应着一个内核线程,程序线程的创建、终止、切换或者同步等线程工作必须自身来完成。它可以做快速的上下文切换。缺点是不能有效利用多核CPU。
 
内核级线程模型





 
这种模型直接调用操作系统的内核线程,所有线程的创建、终止、切换、同步等操作,都由内核来完成。一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源。C++就是这种。
 
两级线程模型





 
这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂,和内核级线程模型类似,一个进程中可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应;这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。

M个用户线程对应N个系统线程,缺点增加了调度器的实现难度。

Go语言的线程模型就是一种特殊的两级线程模型(GPM调度模型)。
 
Go线程实现模型MPG
 
M指的是Machine,一个M直接关联了一个内核线程。由操作系统管理。
P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。
G指的是Goroutine,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。

P的数量由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。

三者关系如下图所示:





 
以上这个图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。为了运行goroutine,线程必须保存上下文。

上下文P(Processor)的数量在启动时设置为GOMAXPROCS环境变量的值或通过运行时函数GOMAXPROCS()。通常情况下,在程序执行期间不会更改。上下文数量固定意味着只有固定数量的线程在任何时候运行Go代码。我们可以使用它来调整Go进程到个人计算机的调用,例如4核PC在4个线程上运行Go代码。

图中P正在执行的Goroutine为蓝色的;处于待执行状态的Goroutine为灰色的,灰色的Goroutine形成了一个队列runqueues。

Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,一旦上下文运行goroutine直到调度点,它会从其runqueue中弹出goroutine,设置堆栈和指令指针并开始运行goroutine。





 
抛弃P(Processor)

你可能会想,为什么一定需要一个上下文,我们能不能直接除去上下文,让Goroutine的runqueues挂到M上呢?答案是不行,需要上下文的目的,是让我们可以直接放开其他线程,当遇到内核线程阻塞的时候。

一个很简单的例子就是系统调用sysall,一个线程肯定不能同时执行代码和系统调用被阻塞,这个时候,此线程M需要放弃当前的上下文环境P,以便可以让其他的Goroutine被调度执行。





 
如上图左图所示,M0中的G0执行了syscall,然后就创建了一个M1(也有可能来自线程缓存),(转向右图)然后M0丢弃了P,等待syscall的返回值,M1接受了P,将·继续执行Goroutine队列中的其他Goroutine。

当系统调用syscall结束后,M0会“偷”一个上下文,如果不成功,M0就把它的Gouroutine G0放到一个全局的runqueue中,将自己置于线程缓存中并进入休眠状态。全局runqueue是各个P在运行完自己的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。

均衡的分配工作

按照以上的说法,上下文P会定期的检查全局的goroutine 队列中的goroutine,以便自己在消费掉自身Goroutine队列的时候有事可做。假如全局goroutine队列中的goroutine也没了呢?就从其他运行的中的P的runqueue里偷。

每个P中的Goroutine不同导致他们运行的效率和时间也不同,在一个有很多P和M的环境中,不能让一个P跑完自身的Goroutine就没事可做了,因为或许其他的P有很长的goroutine队列要跑,得需要均衡。
该如何解决呢?

Go的做法倒也直接,从其他P中偷一半!





 
Goroutine 小结

优点:

1、开销小

POSIX的thread API虽然能够提供丰富的API,例如配置自己的CPU亲和性,申请资源等等,线程在得到了很多与进程相同的控制权的同时,开销也非常的大,在Goroutine中则不需这些额外的开销,所以一个Golang的程序中可以支持10w级别的Goroutine。

每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少(goroutine:2KB ,线程:8MB)

2、调度性能好

在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC。

在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。

缺点:

协程调度机制无法实现公平调度。
 
参考文档:https://segmentfault.com/a/1190000018150987 查看全部
Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

为了更好理解Goroutine,现讲一下线程和协程的概念

线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。

协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。

和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。

Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。
package main

import "fmt"

func main() {

messages := make(chan string)

go func() { messages <- "ping" }()

msg := <-messages
fmt.Println(msg)
}

先给个简单实例
func loop() {
for i := 0; i < ; i++ {
fmt.Printf("%d ", i)
}
}

func main() {
go loop() // 启动一个goroutine
loop()
}
GO并发的实现原理

一、Go并发模型

Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型

CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。

请记住下面这句话:
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”

普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。

Go的CSP并发模型,是通过goroutine和channel来实现的。

goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

生成一个goroutine的方式非常的简单:Go一下,就生成了。
go f();
通信机制channel也很方便,传数据用channel <- data,取数据用<-channel。

在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

示例如下:
package main

import "fmt"

func main() {

messages := make(chan string)

go func() { messages <- "ping" }()

msg := <-messages
fmt.Println(msg)
}
注意 main()本身也是运行了一个goroutine。

messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道

chan 是关键字 代表我要创建一个通道。
 
GO并发模型的实现原理

我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。

我们现在的计算机语言,可以狭义的认为是一种“软件”,它们中所谓的“线程”,往往是用户态的线程,和操作系统本身内核态的线程(简称KSE),还是有区别的。

线程模型的实现,可以分为以下几种方式:

用户级线程模型

341325984-5c6510603f05b_articlex.jpg

 
如图所示,多个用户态的线程对应着一个内核线程,程序线程的创建、终止、切换或者同步等线程工作必须自身来完成。它可以做快速的上下文切换。缺点是不能有效利用多核CPU。
 
内核级线程模型

1778856042-5c65105d351b2_articlex.jpg

 
这种模型直接调用操作系统的内核线程,所有线程的创建、终止、切换、同步等操作,都由内核来完成。一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源。C++就是这种。
 
两级线程模型

2493845633-5c65105970131_articlex.jpg

 
这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂,和内核级线程模型类似,一个进程中可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应;这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。

M个用户线程对应N个系统线程,缺点增加了调度器的实现难度。

Go语言的线程模型就是一种特殊的两级线程模型(GPM调度模型)。
 
Go线程实现模型MPG
 
M指的是Machine,一个M直接关联了一个内核线程。由操作系统管理。
P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。
G指的是Goroutine,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。

P的数量由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。

三者关系如下图所示:

1380329424-5c65106263b83_articlex.jpg

 
以上这个图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。为了运行goroutine,线程必须保存上下文。

上下文P(Processor)的数量在启动时设置为GOMAXPROCS环境变量的值或通过运行时函数GOMAXPROCS()。通常情况下,在程序执行期间不会更改。上下文数量固定意味着只有固定数量的线程在任何时候运行Go代码。我们可以使用它来调整Go进程到个人计算机的调用,例如4核PC在4个线程上运行Go代码。

图中P正在执行的Goroutine为蓝色的;处于待执行状态的Goroutine为灰色的,灰色的Goroutine形成了一个队列runqueues。

Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,一旦上下文运行goroutine直到调度点,它会从其runqueue中弹出goroutine,设置堆栈和指令指针并开始运行goroutine。

2866224357-5c6510c9cc2fe_articlex.jpg

 
抛弃P(Processor)

你可能会想,为什么一定需要一个上下文,我们能不能直接除去上下文,让Goroutine的runqueues挂到M上呢?答案是不行,需要上下文的目的,是让我们可以直接放开其他线程,当遇到内核线程阻塞的时候。

一个很简单的例子就是系统调用sysall,一个线程肯定不能同时执行代码和系统调用被阻塞,这个时候,此线程M需要放弃当前的上下文环境P,以便可以让其他的Goroutine被调度执行。

3568025728-5c65105a25fa0_articlex.jpg

 
如上图左图所示,M0中的G0执行了syscall,然后就创建了一个M1(也有可能来自线程缓存),(转向右图)然后M0丢弃了P,等待syscall的返回值,M1接受了P,将·继续执行Goroutine队列中的其他Goroutine。

当系统调用syscall结束后,M0会“偷”一个上下文,如果不成功,M0就把它的Gouroutine G0放到一个全局的runqueue中,将自己置于线程缓存中并进入休眠状态。全局runqueue是各个P在运行完自己的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。

均衡的分配工作

按照以上的说法,上下文P会定期的检查全局的goroutine 队列中的goroutine,以便自己在消费掉自身Goroutine队列的时候有事可做。假如全局goroutine队列中的goroutine也没了呢?就从其他运行的中的P的runqueue里偷。

每个P中的Goroutine不同导致他们运行的效率和时间也不同,在一个有很多P和M的环境中,不能让一个P跑完自身的Goroutine就没事可做了,因为或许其他的P有很长的goroutine队列要跑,得需要均衡。
该如何解决呢?

Go的做法倒也直接,从其他P中偷一半!

357488702-5c65105c93f2d_articlex.jpg

 
Goroutine 小结

优点:

1、开销小

POSIX的thread API虽然能够提供丰富的API,例如配置自己的CPU亲和性,申请资源等等,线程在得到了很多与进程相同的控制权的同时,开销也非常的大,在Goroutine中则不需这些额外的开销,所以一个Golang的程序中可以支持10w级别的Goroutine。

每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少(goroutine:2KB ,线程:8MB)

2、调度性能好

在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC。

在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。

缺点:

协程调度机制无法实现公平调度。
 
参考文档:https://segmentfault.com/a/1190000018150987

#自学课程#《放学你别走第一季之GO语言学习》Day4打卡

zkbhj 发表了文章 • 0 个评论 • 1371 次浏览 • 2019-02-25 10:13 • 来自相关话题

【Day4任务】
学习GO语言组织数据的容器:数组,切片,映射,列表以及结构体。
用学到的知识实现下面的题目:
设计一个程序,可以录入、读取和删除房源数据,房源数据包括房源名称,房源编号,房源价格(正整数),房源面积(浮点数),是否是首次出租(布尔类型),房源标签(新校区,进地铁,深呼吸,独立卫生间等不限)。
读取房源时通过房源编号读取信息。
打卡时间:2月24日(周日)23:30前
打卡形式:代码运行截图+代码源码
package main

import (
"fmt"
"os"

)

//定义房源结构体
type house struct {
houseCode string
price int
area float64
isNew bool
tags string
}

var (
houseCode string
price int
area float64
isNewName string
isNew bool
tags string
)

func help(){

fmt.Println("请按照提示信息录入房源数据,并根据房源编号显示房源数据:")

}

func line() {
fmt.Println("*********************************")

}

func menu() {
fmt.Println("*********************************")
fmt.Println("1、查询房源(Select)")
fmt.Println("2、新增房源(Insert)")
fmt.Println("3、删除房源(Delete)")
fmt.Println("4、帮助信息(Help)")
fmt.Println("5、统计房源数量(Counts)")
fmt.Println("6、退出程序(Exit)")
fmt.Println("*********************************")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("该房源编号已经被占用,请重新输入房源编号!更多帮助信息请使用参数'help'!")

case 3:
fmt.Println("您要查找的房源信息不存在,请验证房源编号准确性后再试!更多帮助信息请使用参数'help'!")

case 4:
fmt.Println("您要删除的房源数据不存在,请验证房源编号准确性后再试!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func houseCounts(houses map[string]house) {
line()
fmt.Println("【房源数量】您已进入房源数量统计模式")
counts := len(houses)
fmt.Printf("当前已经录入了 %d 套房源信息!", counts)
line()

}

func deleteHouse(houses map[string]house) {
line()
fmt.Println("【删除数据】您已进入房源数据删除模式")
var exists bool
for {
fmt.Println("请输入你要删除的房源编号")
fmt.Scan(&houseCode,)
_, exists = houses[houseCode]
if !exists {
errorHappen(4)
continue
}
break
}
delete(houses, houseCode)
fmt.Printf("编号为%s的房源数据删除成功!", houseCode)

fmt.Println("成功退出房源删除模式,回到主程序!")
line()

}

func insertHouse(houses map[string]house) {
for {
line()
fmt.Println("【新增数据】您已进入新增房源数据模式:")
fmt.Println("1、请输入房源编号")
fmt.Scan(&houseCode,)

_, exit := houses[houseCode]
if exit {
errorHappen(2)
continue
}
break
}


fmt.Println("2、请输入房源价格(正整数,单位:元)")
fmt.Scan(&price,)
fmt.Println("3、请输入房源面积(保留1位小数,单位:平方米)")
fmt.Scan(&area,)

for {
fmt.Println("4、请输入房源是否为首次出租(是 否)")
fmt.Scan(&isNewName,)
if isNewName == "是" {
isNew = true
break
} else if isNewName == "否" {
isNew = false
break
} else {
errorHappen(1)
}
}
fmt.Println("5、请输入房源标签并用','隔开")
fmt.Scan(&tags,)


house := house{
houseCode,
price,
area,
isNew,
tags,
}

houses[house.houseCode] = house
fmt.Println("恭喜您!房源"+ house.houseCode +"的房源信息保存成功!")
fmt.Println("成功退出房源保存模式,回到主程序!")
line()

}

func readHouse(houses map[string]house) {
line()
fmt.Println("【查询数据】您已进入查询房源数据模式:")
var (
house house
exists bool
)
for {
fmt.Println("请输入你要查询的房源编号")
fmt.Scan(&houseCode,)
house, exists = houses[houseCode]
if !exists {
errorHappen(3)
continue
}
break
}

fmt.Println("已为您查询到该房源数据,具体房源信息如下:")
fmt.Println("【房源编号】" + house.houseCode)
fmt.Printf("【房源价格】%d 元/月\n", house.price)
fmt.Printf("【房源面积】 %f 平方米\n", house.area)
if house.isNew {
fmt.Println( "【首次出租】是")
} else {
fmt.Println( "【首次出租】否")
}

fmt.Println( "【房源标签】:" + house.tags)
fmt.Println("成功退出房源查询模式,回到主程序!")
line()


}



func main(){

const helpFlag = "help"
var action string



//定义map存储房源数据
houses := map[string]house{}

//进入主程序控制流程
fmt.Println("欢迎进入凯冰科技房源管理系统")
line()

for {
fmt.Println("当前可进行的操作如下,请输入对应的数字编号:")
menu()

fmt.Scan(&action,)

switch action {
case "1":
readHouse(houses)
continue

case "2":
insertHouse(houses)
continue

case "3":
deleteHouse(houses)
continue

case "4":
help()
continue

case "5":
houseCounts(houses)

case "6":
fmt.Println("您已成功退出程序运行!感谢您的使用!")
os.Exit(0)
default:
errorHappen(1)
}
}

}




















  查看全部
【Day4任务】
学习GO语言组织数据的容器:数组,切片,映射,列表以及结构体。
用学到的知识实现下面的题目:
设计一个程序,可以录入、读取和删除房源数据,房源数据包括房源名称,房源编号,房源价格(正整数),房源面积(浮点数),是否是首次出租(布尔类型),房源标签(新校区,进地铁,深呼吸,独立卫生间等不限)。
读取房源时通过房源编号读取信息。
打卡时间:2月24日(周日)23:30前
打卡形式:代码运行截图+代码源码
package main

import (
"fmt"
"os"

)

//定义房源结构体
type house struct {
houseCode string
price int
area float64
isNew bool
tags string
}

var (
houseCode string
price int
area float64
isNewName string
isNew bool
tags string
)

func help(){

fmt.Println("请按照提示信息录入房源数据,并根据房源编号显示房源数据:")

}

func line() {
fmt.Println("*********************************")

}

func menu() {
fmt.Println("*********************************")
fmt.Println("1、查询房源(Select)")
fmt.Println("2、新增房源(Insert)")
fmt.Println("3、删除房源(Delete)")
fmt.Println("4、帮助信息(Help)")
fmt.Println("5、统计房源数量(Counts)")
fmt.Println("6、退出程序(Exit)")
fmt.Println("*********************************")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("该房源编号已经被占用,请重新输入房源编号!更多帮助信息请使用参数'help'!")

case 3:
fmt.Println("您要查找的房源信息不存在,请验证房源编号准确性后再试!更多帮助信息请使用参数'help'!")

case 4:
fmt.Println("您要删除的房源数据不存在,请验证房源编号准确性后再试!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func houseCounts(houses map[string]house) {
line()
fmt.Println("【房源数量】您已进入房源数量统计模式")
counts := len(houses)
fmt.Printf("当前已经录入了 %d 套房源信息!", counts)
line()

}

func deleteHouse(houses map[string]house) {
line()
fmt.Println("【删除数据】您已进入房源数据删除模式")
var exists bool
for {
fmt.Println("请输入你要删除的房源编号")
fmt.Scan(&houseCode,)
_, exists = houses[houseCode]
if !exists {
errorHappen(4)
continue
}
break
}
delete(houses, houseCode)
fmt.Printf("编号为%s的房源数据删除成功!", houseCode)

fmt.Println("成功退出房源删除模式,回到主程序!")
line()

}

func insertHouse(houses map[string]house) {
for {
line()
fmt.Println("【新增数据】您已进入新增房源数据模式:")
fmt.Println("1、请输入房源编号")
fmt.Scan(&houseCode,)

_, exit := houses[houseCode]
if exit {
errorHappen(2)
continue
}
break
}


fmt.Println("2、请输入房源价格(正整数,单位:元)")
fmt.Scan(&price,)
fmt.Println("3、请输入房源面积(保留1位小数,单位:平方米)")
fmt.Scan(&area,)

for {
fmt.Println("4、请输入房源是否为首次出租(是 否)")
fmt.Scan(&isNewName,)
if isNewName == "是" {
isNew = true
break
} else if isNewName == "否" {
isNew = false
break
} else {
errorHappen(1)
}
}
fmt.Println("5、请输入房源标签并用','隔开")
fmt.Scan(&tags,)


house := house{
houseCode,
price,
area,
isNew,
tags,
}

houses[house.houseCode] = house
fmt.Println("恭喜您!房源"+ house.houseCode +"的房源信息保存成功!")
fmt.Println("成功退出房源保存模式,回到主程序!")
line()

}

func readHouse(houses map[string]house) {
line()
fmt.Println("【查询数据】您已进入查询房源数据模式:")
var (
house house
exists bool
)
for {
fmt.Println("请输入你要查询的房源编号")
fmt.Scan(&houseCode,)
house, exists = houses[houseCode]
if !exists {
errorHappen(3)
continue
}
break
}

fmt.Println("已为您查询到该房源数据,具体房源信息如下:")
fmt.Println("【房源编号】" + house.houseCode)
fmt.Printf("【房源价格】%d 元/月\n", house.price)
fmt.Printf("【房源面积】 %f 平方米\n", house.area)
if house.isNew {
fmt.Println( "【首次出租】是")
} else {
fmt.Println( "【首次出租】否")
}

fmt.Println( "【房源标签】:" + house.tags)
fmt.Println("成功退出房源查询模式,回到主程序!")
line()


}



func main(){

const helpFlag = "help"
var action string



//定义map存储房源数据
houses := map[string]house{}

//进入主程序控制流程
fmt.Println("欢迎进入凯冰科技房源管理系统")
line()

for {
fmt.Println("当前可进行的操作如下,请输入对应的数字编号:")
menu()

fmt.Scan(&action,)

switch action {
case "1":
readHouse(houses)
continue

case "2":
insertHouse(houses)
continue

case "3":
deleteHouse(houses)
continue

case "4":
help()
continue

case "5":
houseCounts(houses)

case "6":
fmt.Println("您已成功退出程序运行!感谢您的使用!")
os.Exit(0)
default:
errorHappen(1)
}
}

}

微信图片_20190225101127.png


微信图片_20190225101132.png


微信图片_20190225101137.png


微信图片_20190225101147.png

 

#自学课程#《放学你别走第一季之GO语言学习》Day3打卡

zkbhj 发表了文章 • 0 个评论 • 1259 次浏览 • 2019-02-25 10:11 • 来自相关话题

【Day3任务】
学习GO语言的流程控制和函数。
用学到的知识实现下面的题目:
要求用户输入一个年份和一个月份,判断(要求使用嵌套的if…else和switch分别判断一次)该年该月有多少天。
打卡时间:2月23日(周六)23:30前
打卡形式:代码运行截图+代码源码
package main

import (
"fmt"
"strconv"
"os"
)

func help(){

fmt.Println("请依次按顺序输入年份和月份2个参数,用空格隔开,例如要查看2019年2月的天数,请输入:2019 2")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("月份为大于0小于13的正整数,您输入的月份不合法!更多帮助信息请使用参数'help'!")

case 3:
fmt.Println("年份为大于0的正整数,您输入的年份不合法!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func isLeap(year int) int {

isLeap := 0
if year % 4 == 0 && year % 100 != 0 {
isLeap = 1
}

return isLeap

}

func main(){

const helpFlag = "help"


args := os.Args

//输入帮助符号
if len(args) == 2 && helpFlag == args[1] {
help()
os.Exit(0)
}

//判断参数传递是否正确
if len(args) < 2 || args == nil {
errorHappen(1)
os.Exit(0)

}




//获取数据
year, _ := strconv.Atoi(args[1])
month := args[2]
days := 0

//判断月份合法
m,_ := strconv.Atoi(args[2])
if m < 0 || m >12{
errorHappen(2)
os.Exit(0)
}


//判断年份合法
if year < 0 {
errorHappen(3)
os.Exit(0)
}

//使用switch case 实现流程控制
switch month {

case "1","3","5","7","8","10","12":{
days = 31

}

case "4","6","9","11":{
days = 30
}

case "2" : {
if 1 == isLeap(year) {
days = 29
}else{
days = 28
}

}

default:
help()

}

//打印结果
fmt.Printf("%d年%s月一共有%d天!",year,month,days)

}





  查看全部
【Day3任务】
学习GO语言的流程控制和函数。
用学到的知识实现下面的题目:
要求用户输入一个年份和一个月份,判断(要求使用嵌套的if…else和switch分别判断一次)该年该月有多少天。
打卡时间:2月23日(周六)23:30前
打卡形式:代码运行截图+代码源码
package main

import (
"fmt"
"strconv"
"os"
)

func help(){

fmt.Println("请依次按顺序输入年份和月份2个参数,用空格隔开,例如要查看2019年2月的天数,请输入:2019 2")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("月份为大于0小于13的正整数,您输入的月份不合法!更多帮助信息请使用参数'help'!")

case 3:
fmt.Println("年份为大于0的正整数,您输入的年份不合法!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func isLeap(year int) int {

isLeap := 0
if year % 4 == 0 && year % 100 != 0 {
isLeap = 1
}

return isLeap

}

func main(){

const helpFlag = "help"


args := os.Args

//输入帮助符号
if len(args) == 2 && helpFlag == args[1] {
help()
os.Exit(0)
}

//判断参数传递是否正确
if len(args) < 2 || args == nil {
errorHappen(1)
os.Exit(0)

}




//获取数据
year, _ := strconv.Atoi(args[1])
month := args[2]
days := 0

//判断月份合法
m,_ := strconv.Atoi(args[2])
if m < 0 || m >12{
errorHappen(2)
os.Exit(0)
}


//判断年份合法
if year < 0 {
errorHappen(3)
os.Exit(0)
}

//使用switch case 实现流程控制
switch month {

case "1","3","5","7","8","10","12":{
days = 31

}

case "4","6","9","11":{
days = 30
}

case "2" : {
if 1 == isLeap(year) {
days = 29
}else{
days = 28
}

}

default:
help()

}

//打印结果
fmt.Printf("%d年%s月一共有%d天!",year,month,days)

}

微信图片_20190225101021.png

 

常用的GO语言内部包解析

zkbhj 发表了文章 • 0 个评论 • 1657 次浏览 • 2019-02-25 10:08 • 来自相关话题

一、Go 标准库可以大致按其中库的功能进行以下粗略的分类


输入输出:这个分类包括二进制以及文本格式在屏幕、键盘、文件以及其他设备上的输
入输出等,比如二进制文件的读写。对应于此分类的包有bufio、 fmt、 io、 log和flag
等,其中 flag 用于处理命令行参数。

文本处理:这个分类包括字符串和文本内容的处理,比如字符编码转换等。对应于此分
类的包有encoding、 bytes、 strings、 strconv、 text、 mime、 unicode、 regexp、
index和path等。其中path用于处理路径字符串。

网络:这个分类包括开发网络程序所需要的包,比如Socket编程和网站开发等。对应于此
分类的包有: net、 http和expvar等。

系统:这个分类包含对系统功能的封装,比如对操作系统的交互以及原子性操作等。对
应于此分类的包有os、 syscall、 sync、 time和unsafe等。

数据结构与算法:对应于此分类的包有math、 sort、 container、 crypto、 hash、
archive、 compress和image等。因为image包里提供的图像编解码都是算法,所以也
归入此类。

运行时:对应于此分类的包有: runtime、 reflect和go等。

 二、常用包介绍
这里介绍Go语言标准库里使用频率相对较高的一些包 (如下):
 
fmt。它实现了格式化的输入输出操作,其中的fmt.Printf()和fmt.Println()是开发者使用最为频繁的函数。io。它实现了一系列非平台相关的IO相关接口和实现,比如提供了对os中系统相关的IO功能的封装。我们在进行流式读写(比如读写文件)时,通常会用到该包。bufio。它在io的基础上提供了缓存功能。在具备了缓存功能后, bufio可以比较方便地提供ReadLine之类的操作。strconv。本包提供字符串与基本数据类型互转的能力。os。本包提供了对操作系统功能的非平台相关访问接口。接口为Unix风格。提供的功能包括文件操作、进程管理、信号和用户账号等。sync。它提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。flag。它提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。encoding/json。 JSON目前广泛用做网络程序中的通信格式。本包提供了对JSON的基本支持,比如从一个对象序列化为JSON字符串,或者从JSON字符串反序列化出一个具体的对象等。http。它是一个强大而易用的包,也是Golang语言是一门“互联网语言”的最好佐证。通过http包,只需要数行代码,即可实现一个爬虫或者一个Web服务器,这在传统语言中是无法想象的。
 
三、完整包列表













































 
  查看全部
一、Go 标准库可以大致按其中库的功能进行以下粗略的分类



输入输出:这个分类包括二进制以及文本格式在屏幕、键盘、文件以及其他设备上的输
入输出等,比如二进制文件的读写。对应于此分类的包有bufio、 fmt、 io、 log和flag
等,其中 flag 用于处理命令行参数。

文本处理:这个分类包括字符串和文本内容的处理,比如字符编码转换等。对应于此分
类的包有encoding、 bytes、 strings、 strconv、 text、 mime、 unicode、 regexp、
index和path等。其中path用于处理路径字符串。

网络:这个分类包括开发网络程序所需要的包,比如Socket编程和网站开发等。对应于此
分类的包有: net、 http和expvar等。

系统:这个分类包含对系统功能的封装,比如对操作系统的交互以及原子性操作等。对
应于此分类的包有os、 syscall、 sync、 time和unsafe等。

数据结构与算法:对应于此分类的包有math、 sort、 container、 crypto、 hash、
archive、 compress和image等。因为image包里提供的图像编解码都是算法,所以也
归入此类。

运行时:对应于此分类的包有: runtime、 reflect和go等。


 二、常用包介绍
这里介绍Go语言标准库里使用频率相对较高的一些包 (如下):
 
  1. fmt。它实现了格式化的输入输出操作,其中的fmt.Printf()和fmt.Println()是开发者使用最为频繁的函数。
  2. io。它实现了一系列非平台相关的IO相关接口和实现,比如提供了对os中系统相关的IO功能的封装。我们在进行流式读写(比如读写文件)时,通常会用到该包。
  3. bufio。它在io的基础上提供了缓存功能。在具备了缓存功能后, bufio可以比较方便地提供ReadLine之类的操作。
  4. strconv。本包提供字符串与基本数据类型互转的能力。
  5. os。本包提供了对操作系统功能的非平台相关访问接口。接口为Unix风格。提供的功能包括文件操作、进程管理、信号和用户账号等。
  6. sync。它提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。
  7. flag。它提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。
  8. encoding/json。 JSON目前广泛用做网络程序中的通信格式。本包提供了对JSON的基本支持,比如从一个对象序列化为JSON字符串,或者从JSON字符串反序列化出一个具体的对象等。
  9. http。它是一个强大而易用的包,也是Golang语言是一门“互联网语言”的最好佐证。通过http包,只需要数行代码,即可实现一个爬虫或者一个Web服务器,这在传统语言中是无法想象的。

 
三、完整包列表

20181122133038383.png


20181122133121371.png


20181122133138459.png


20181122133154889.png


20181122133210116.png


20181122133225130.png


20181122133237835.png


20181122133316248.png


20181122133339570.png

 
 

#自学课程#《放学你别走第一季之GO语言学习》Day2打卡

zkbhj 发表了文章 • 0 个评论 • 1435 次浏览 • 2019-02-22 14:54 • 来自相关话题

【Day2任务】
学习GO语言的基础数据类型,命名,变量,常量,格式化输入输出,类型转换及别名;运算符。
用学到的知识实现下面的题目:
用Go语言实现一个计算器程序,实现两个操作数的加、减、乘、除四种基本类型的操作,并可以指定最终计算结果的数据类型。
打卡时间:后天(2月22日)18:30
打卡形式:代码运行截图+现场面对面讨论package main

import (
"fmt"
"strconv"
"os"
)

func help(){

fmt.Println("请依次按顺序输入运算方法(add,sub,mul,div),数字1,数字2这三个参数,用空格隔开")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("除法计算时,除数不能为0!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func main(){

const HELP_FLAG = "help"
const PRECISION = 2


args := os.Args

//输入帮助符号
if len(args) == 2 && HELP_FLAG == args[1] {
help()
os.Exit(0)
}

//判断参数传递是否正确
if len(args) < 3 || args == nil {
errorHappen(1)
os.Exit(0)

}


//获取计算方法
operator := args[1]
result := 0.0

//使用switch case 实现流程控制
switch operator {

case "add":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 + number2

}

}


case "sub":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 - number2

}

}

case "mul":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 * number2

}

}

case "div":{

//判断除数是否为0
if "0" == args[3] {
errorHappen(2)
os.Exit(0)
}

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 / number2

}

}

default:
help()

}

//打印结果
fmt.Println("计算结果为:",result)

}





  查看全部
【Day2任务】
学习GO语言的基础数据类型,命名,变量,常量,格式化输入输出,类型转换及别名;运算符。
用学到的知识实现下面的题目:
用Go语言实现一个计算器程序,实现两个操作数的加、减、乘、除四种基本类型的操作,并可以指定最终计算结果的数据类型。
打卡时间:后天(2月22日)18:30
打卡形式:代码运行截图+现场面对面讨论
package main

import (
"fmt"
"strconv"
"os"
)

func help(){

fmt.Println("请依次按顺序输入运算方法(add,sub,mul,div),数字1,数字2这三个参数,用空格隔开")

}

func errorHappen(no int) {

switch no {

case 1:
fmt.Println("参数错误!更多帮助信息请使用参数'help'!")

case 2:
fmt.Println("除法计算时,除数不能为0!更多帮助信息请使用参数'help'!")

default:
fmt.Println("未知错误!更多帮助信息请使用参数'help'!")
}
}

func main(){

const HELP_FLAG = "help"
const PRECISION = 2


args := os.Args

//输入帮助符号
if len(args) == 2 && HELP_FLAG == args[1] {
help()
os.Exit(0)
}

//判断参数传递是否正确
if len(args) < 3 || args == nil {
errorHappen(1)
os.Exit(0)

}


//获取计算方法
operator := args[1]
result := 0.0

//使用switch case 实现流程控制
switch operator {

case "add":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 + number2

}

}


case "sub":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 - number2

}

}

case "mul":{

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 * number2

}

}

case "div":{

//判断除数是否为0
if "0" == args[3] {
errorHappen(2)
os.Exit(0)
}

number1, error1 := strconv.ParseFloat(args[2], PRECISION)
number2, error2 := strconv.ParseFloat(args[3], PRECISION)

if error1 == nil && error2 == nil {
result = number1 / number2

}

}

default:
help()

}

//打印结果
fmt.Println("计算结果为:",result)

}

微信图片_20190222145306.png

 

#自学课程#《放学你别走第一季之GO语言学习》Day1打卡

zkbhj 发表了文章 • 0 个评论 • 1407 次浏览 • 2019-02-22 14:53 • 来自相关话题

【Day1任务】
熟悉Go编程语言,搭建运行环境,选择适合自己的IDE,并写出自己的第一个GO程序(hello world!),并用命令形式成功运行。
打卡时间:明天(2月21日)18:30
打卡形式:程序运行截图+现场面对面讨论package main

import "fmt"

func main(){
fmt.Printf("%s\n","Hello wolrd!This is my first go program!I'm ZhengKai!")
} 查看全部
【Day1任务】
熟悉Go编程语言,搭建运行环境,选择适合自己的IDE,并写出自己的第一个GO程序(hello world!),并用命令形式成功运行。
打卡时间:明天(2月21日)18:30
打卡形式:程序运行截图+现场面对面讨论
package main

import "fmt"

func main(){
fmt.Printf("%s\n","Hello wolrd!This is my first go program!I'm ZhengKai!")
}