精华内容
下载资源
问答
  • go并发编程实战

    2018-11-01 17:44:56
    go并发编程实战。并发是公司发展到一定规模必然碰到的难题。也许你能通过这本书有所收获
  • Go并发编程实战

    2017-08-15 14:55:00
    Go并发编程实战

    Go并发编程实战

    展开全文
  • Go 并发编程实战

    2017-12-11 09:55:51
    Go 语言并发编程实战,中文版,希望共同学习,一起进步。
  • go 并发编程实战 mobi

    2018-09-08 17:05:18
    go 并发编程实战go 并发编程实战 mobi kindle 实测,放心下载
  • 图灵原创——《GO并发编程实战》第二版。此书重点描述多进程和多线程编程相关的知识,此书基于go 1.8版本全面更新,结合实际代码例子,更深入秒回Go运行时系统内部原理,本文档附带书中go源码。对于Go语言编程感兴趣...
  • go 并发编程实战-郝林

    2017-10-12 23:41:45
    这本书对Go语言并发编程的探讨之深入,讲解之细腻是它的一大亮点!
  • Go并发编程实战》读书笔记-初识Go语言  作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。  在讲解怎样用Go语言之前,我们先介绍Go语言的特性,基础概念和标准命令。 一.语言特性  ...

                  《Go并发编程实战》读书笔记-初识Go语言

                                             作者:尹正杰 

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

     

      在讲解怎样用Go语言之前,我们先介绍Go语言的特性,基础概念和标准命令。

     

    一.语言特性

      我们可以用几个关键词或短语来概括Go语言的主要特性。

    1>.开放源代码

      这显示来Go作者开放的态度以及营造语言生态的决心。顺便说一句,Go本事就是用Go语言编写的。

    2>.静态类型和编译类型

      在Go中,每个变量或常量都必须在声明时指定类型,且不可改变。另外,程序必须通过编译生成归档文件或可执行文件,而后才能被使用或执行。不过,其语法非常简洁,就像一些解释型脚本语言那样,易学易用。

    3>.跨平台

      这主要是指跨计算架构和操作系统。目前,它已经支持对大部分主流的计算架构和操作系统,并且这个范围还在不断的扩大。只要下载与之对应的Go语言安装包,并且经过简单的安装和设置,就可以使Go就绪了。除此之外,在编写Go语言程序的过程中,我们几乎感觉不到平台的差异。

    4>.自动垃圾回收

      程序在允运行过程中的垃圾回收工作一般由Go运行时系统全权负责。不过,Go也允许我们对此项工作进行干预。

    5>.原生的并发编程

      拥有自己的并发编程模型,其主要组成本分由goroutime(也可以称为Go例程)和channel(也可称为通道)。另外,还拥有一个特殊的关键字go。

    6>.完善的构建工具

      它自带来很多强大的命令和工具,通过他们,可以轻松地完成Go程序的获取,编译,测试,安装,运行,分析等一系列工作。

    7>.多编程范式

      Go支持函数式编程。函数类型为第一等类型,可以方便地传递和赋值。此外,它还支持面向对象编程,由接口类型与实现类型的概念,但用于嵌入替值。此外,它还支持面向对象编程,有接口类型与实现类型的概念,但用嵌入替代来集成。

    8>.代码风格强制统一

      Go安装包中有自己的代码格式化工具,可以用来统一程序的编码风格。

    9>.高效的编程和运行

      Go简洁,直接的语法使我们可以快速编写程序。加之它强大的运行时系统,程序可以充分利用计算环境飞快运行。

    10>.丰富的标准库

      Go是通用的编程语言,其标准库中有很多开箱即用的API。尤其是编写诸如系统级程序,Web程序和分布式程序时,我们几乎无需依赖第三方库。

     

    二.安装和设置

      安装Go相当简单,只要你的操作系统被Go语言支持即可。Go语言官方网站放出的每一个版本都会有主流平台的二进制安装包以及源码包,你可以自行选择对应的文件下载。Go的下载地址为:https://golang.google.cn/dl/(国内下载地址)https://golang.org/dl/(国外下载地址,需VPN)

      如上图所示,我们从下载地址中挑选了“go1.11.4.darwin-amd64.tar.gz”文件并下载。注意,这个文件的名称中有两个关键词,一个是“darwin”,一个是“adm64”。前者标示该文件对应的操作系统种类,其他的同类词有“linux”,“freebsd”,“windows”等。或者标示该文件对应的计算架构种类,其中“adm64”表示64位的计算架构,另一个是386,表示32位的计算架构。实际上,这里的计算架构和操作系统可以统称为Go的计算平台或者计算环境。

      解压该文件时会得到一个名为go的文件夹,其中包含所有的Go语言相关文件。该文件夹下还有很多文件夹和文件,下面简要说明其中主要文件夹的功用。如果你是Linux用户或者是window用户的话可参考我之前的笔记:初探GO语言

    bogon:~ yinzhengjie$ sudo mkdir /yinzhengjie
    Password:
    bogon:~ yinzhengjie$ 
    bogon:~ yinzhengjie$ sudo chown yinzhengjie /yinzhengjie/ 
    bogon:~ yinzhengjie$ 
    bogon:~ yinzhengjie$ ls -ld /yinzhengjie/
    drwxr-xr-x  2 yinzhengjie  wheel  64 12 19 15:26 /yinzhengjie/
    bogon:~ yinzhengjie$ 
    bogon:~ yinzhengjie$ mkdir /yinzhengjie/softwares/                          #我习惯性将数据都存放到该目录下,因此我们需要创建出来它!
    bogon:~ yinzhengjie$ 
    bogon:~ yinzhengjie$ tar -zxf go1.11.4.darwin-amd64.tar.gz -C /yinzhengjie/softwares/
    bogon:~ yinzhengjie$ 
    bogon:~ yinzhengjie$ cd /yinzhengjie/softwares/go/
    bogon:go yinzhengjie$ pwd
    /yinzhengjie/softwares/go
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ ls -l
    total 320
    -rw-r--r--    1 yinzhengjie  wheel  55284 12 15 07:37 AUTHORS
    -rw-r--r--    1 yinzhengjie  wheel   1339 12 15 07:37 CONTRIBUTING.md
    -rw-r--r--    1 yinzhengjie  wheel  71070 12 15 07:37 CONTRIBUTORS
    -rw-r--r--    1 yinzhengjie  wheel   1479 12 15 07:37 LICENSE
    -rw-r--r--    1 yinzhengjie  wheel   1303 12 15 07:37 PATENTS
    -rw-r--r--    1 yinzhengjie  wheel   1607 12 15 07:37 README.md
    -rw-r--r--    1 yinzhengjie  wheel      8 12 15 07:37 VERSION
    drwxr-xr-x   17 yinzhengjie  wheel    544 12 15 07:37 api          #用于存放依照Go版本顺序的API增量列表文件。这里所说的API包含公开的变量,常量,函数等。这些API增量列表文件用于Go语言API检查。
    drwxr-xr-x    5 yinzhengjie  wheel    160 12 15 07:49 bin          #用于存放祝哟啊的标准命令文件,包括go,godoc和gofmt。
    drwxr-xr-x   48 yinzhengjie  wheel   1536 12 15 07:37 doc          #用于存放标准库的HTML格式的程序文档。我们可以通过godoc命令启动一个Web程序展现这些文档。
    -rw-r--r--    1 yinzhengjie  wheel   5686 12 15 07:37 favicon.ico
    drwxr-xr-x    3 yinzhengjie  wheel     96 12 15 07:37 lib          #用于存放一些特殊的库文件。
    drwxr-xr-x   17 yinzhengjie  wheel    544 12 15 07:49 misc          #用于存放一些辅助类的说明和工具。
    drwxr-xr-x    8 yinzhengjie  wheel    256 12 15 07:49 pkg          #用于存放安装Go标准库的所有归档文件。注意,你会发现其中有名称为“darwin_amd64”的文件夹,我们成为平台相关目录。可以看到,这类文件夹的名称由对应的操作系统和计算架构的名称组合而成。通过“go install”命令,Go程序(这里是标准库中的程序)会被编译程成平台相关的归档文件存放到其中。另外,“pkg/tool/darwin_amd64”文件夹存放了使用Go制作软件时用到的很多强大的命令和工具。
    -rw-r--r--    1 yinzhengjie  wheel     26 12 15 07:37 robots.txt
    drwxr-xr-x   68 yinzhengjie  wheel   2176 12 15 07:37 src          #用于存放Go本身,Go标准工具以及标准库的所有源码文件。深入探究Go,就考它了。
    drwxr-xr-x  304 yinzhengjie  wheel   9728 12 15 07:49 test          #存放用来测试和验证Go本身的所有相关文件。
    bogon:go yinzhengjie$ 

      现在,你已经大致了解了go文件夹中的目录结构及其用途。我们需要把这个目录放在一个适当的位置,在Linux操作系统,Go官方推荐的目录是“/usr/local”,这也是我们源码安装惯于存放的路径。而我习惯将第三方下载的文件存放在指定的目录中“/yinzhengjie/softwares/”目录下。在这之后,我们需要设置一个环境变量,即:GOROOT。GOROOT的之应该是Go的根目录。这里是“/yinzhengjie/softwares/go”。另外,环境变量中PATH中也应该增加一项,即“$GOROOT/bin”。这样就可以在任意目录使用那几个Go命令了。具体操作如下:

    bogon:go yinzhengjie$ pwd
    /yinzhengjie/softwares/go
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ ls -l
    total 320
    -rw-r--r--    1 yinzhengjie  wheel  55284 12 15 07:37 AUTHORS
    -rw-r--r--    1 yinzhengjie  wheel   1339 12 15 07:37 CONTRIBUTING.md
    -rw-r--r--    1 yinzhengjie  wheel  71070 12 15 07:37 CONTRIBUTORS
    -rw-r--r--    1 yinzhengjie  wheel   1479 12 15 07:37 LICENSE
    -rw-r--r--    1 yinzhengjie  wheel   1303 12 15 07:37 PATENTS
    -rw-r--r--    1 yinzhengjie  wheel   1607 12 15 07:37 README.md
    -rw-r--r--    1 yinzhengjie  wheel      8 12 15 07:37 VERSION
    drwxr-xr-x   17 yinzhengjie  wheel    544 12 15 07:37 api
    drwxr-xr-x    5 yinzhengjie  wheel    160 12 15 07:49 bin
    drwxr-xr-x   48 yinzhengjie  wheel   1536 12 15 07:37 doc
    -rw-r--r--    1 yinzhengjie  wheel   5686 12 15 07:37 favicon.ico
    drwxr-xr-x    3 yinzhengjie  wheel     96 12 15 07:37 lib
    drwxr-xr-x   17 yinzhengjie  wheel    544 12 15 07:49 misc
    drwxr-xr-x    8 yinzhengjie  wheel    256 12 15 07:49 pkg
    -rw-r--r--    1 yinzhengjie  wheel     26 12 15 07:37 robots.txt
    drwxr-xr-x   68 yinzhengjie  wheel   2176 12 15 07:37 src
    drwxr-xr-x  304 yinzhengjie  wheel   9728 12 15 07:49 test
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ sudo vi /etc/profile 
    Password:
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ tail -3 /etc/profile    #编辑系统环境变量的配置文件
    #Add by yinzhengjie
    GOROOT=/yinzhengjie/softwares/go
    export PATH=$PATH:$GOROOT/bin
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ source /etc/profile     #重新家在配置文件
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ go version          #查看golang的版本
    go version go1.11.4 darwin/amd64
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ 

      至此,Go已经安装好了,下面重点了解一下Go中的一些基本概念。

     

    三.工程结构

      Go是一门推崇软件工程理念的编程语言,它为开发周期的每个环节都提供了完备的工具和支持。Go 语言高度强调代码和项目的规范和统一,这集中体现在工程结构或者说代码体制的细节之处。Go也是一门开放的语言,它本身就是开源软件。更重要的是,它让开发人员很容易通过“go get”命令从公共代码库(比如著名的代码托管网站GitHub)中下载开源代码并使用。这除了得益于Go自带命令的强大之外,还应该归功于Go工程结构的严谨和完善。

    1>.工作区

      一般情况下,Go源码文件必须放在工作区中。但是对于命令源码文件来说,这不是必需的。工作区其实就是一个对应特定工程的目录,它应包含3个子目录:

      1.1>.src目录

        用于以代码包的形式组织并保存Go源码文件,这里的代码包与src下子目录一一对应。例如,若一个源码文件被声明属于代码包log,那么它就应当保存在“src/log”目录中。当然,你也可以把Go源码文件直接放在src目录下,但这样的Go源码文件就只能被声明属于main代码包了。除非用于临时测试或演示,一般还是建议把Go源码文件存入特定的代码包中。

      1.2>.pkg目录

        用于存放通过go install命令安装后的代码包的归档文件。前提是代码包中必需包含Go库源码文件。归档文件是指那些名称以“.a”结尾的文件。该目录与GOROOT目录下的pkg目录功能类似。区别在于,工作区中的pkg目录专门用来存放用户代码的归档文件。编译和安装用户代码的过程一般会以代码包为单位进行。比如log包被编译安装后,将生成一个名为log.a的归档文件,并存放在档期啊你工作区的pkg目录喜爱的平台相关目录中。

      1.3>.bin目录

        与pkg目录类似,在通过“go install”命令完成安装后,保存由Go命令源码文件生成的可执行文件。在类Unix操作系统下,这个可执行文件一般来说名称与源码文件的主文件名相同。而在windows操作系统下,这个可执行文件的名称则是源码文件主文件名加“.exe”后缀。

      注意:这里有必要声明一些Go语言的命令源码和库源码文件的区别。所谓命令源码文件,就是声明属于main代码包并且包含无参数声明和结果声明的main函数的源码文件。这类源码文件是程序的入口,它们可以独立运行(使用“go run”命令),也可以通过“go build”或者“go install”命令得到相应的可执行文件。所谓库源码文件,则是指存在与某个代码包中的普通源码文件。

    2>.GOPATH

       我们需要将工作区的目录路径添加到环境变量GOPATH中。否则,即使处于同一工作区(事实上,未被加入GOPATH中的目录不用管成为工作区),代码之间也无法通过绝对代码包路径调用。在实际开发环境中,工作区可以只有一个,也可以由多个,这些工作区的目录路径都需要添加到GOPATH中。与GOROOT一样,我们应确保GOPATH一直有效。

      注意:GOPATH中不要包含GO语言的根目录,以便将Go语言本身的工作区同用户工作区严格分开。通过Go工具中的代码获取命令“go get”,可以指定项目的代码下载到我们在GOPATH中设定的第一个工作区中,并在其中完成编译和安装。

     

    bogon:go yinzhengjie$ mkdir /yinzhengjie/workspace
    bogon:go yinzhengjie$ mkdir /yinzhengjie/workspace/src
    bogon:go yinzhengjie$ mkdir /yinzhengjie/workspace/pkg
    bogon:go yinzhengjie$ mkdir /yinzhengjie/workspace/bin
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ sudo vi /etc/profile 
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ tail -3 /etc/profile 
    #Add GOPATH Path by yinzhengjie
    GOPATH=/yinzhengjie/workspace
    export PATH=$PATH:$GOPATH
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ source /etc/profile 
    bogon:go yinzhengjie$ 
    bogon:go yinzhengjie$ echo $GOPATH
    /yinzhengjie/workspace
    bogon:go yinzhengjie$  

    3>.源码文件

      Go 源码文件有3个种类,即命令源码文件,库源码文件和测试源码文件,下面我们详细说明这3类源码文件。

      3.1>.命名源码文件

        如果一个源码文件被声明属于一个main包,且该文件代码中包含无参数声明和结果声明的main函数,则它是是命令源码文件。命令源码文件可通过“go run”命令直接运行。

      3.2>.库源码文件

        通常,库源码文件声明的包名会与它直接所属的代码包(目录)名一致,且库源码文件中不包含无参数声明和无结果声明的main函数。

      3.3>.测试源码文件

        测试源码文件是一种特殊的库文件,可以通过“go test”命令运行当前代码包下的所有测试源码文件。成为测试源码文件的充分必要条件有两个。一个是文件名需要以“_test.go”结尾。另一个是文件中需要至少包含一个名称以Test开头或Benchmark开头,且拥有一个类型为“*testing.T”或“*testing.B”的参数的函数。“*testing.T”和“*testing.B”是两个结构体类型。而“*testing.T”和“*testing.B”则分别为前两者的指针类型。它们分别为前两者的指针类型。它们分别是功能测试和基准测试所需的。

      最后插一句,Go代码的文本文件需要以UTF-8编码存储。如果源码文件中出现来非UTF-8编码的字符,那么在运行,编译或安装的时候,Go命令会抛出“illegal UTF-8 squence”错误。

     

    四.标准命令简述

       Go本身包含来大量用于处理Go程序的命令和工具。go命令就是其中最常用的一个,它有许多子命令,下面来系统的了解一下吧:

    1>.build

       用于编译指定的代码包或Go语言源码文件。命令源码文件会被编译成可执行文件,并存放到命令执行的目录或指定目录下。而库源码文件被编译后,则不会在非临时目录中留下任何文件。

    2>.clean

       用于清除因执行其他go命令而遗留下来的临时目录和文件。

    3>.doc

       用于显示打印Go语言代码包以及程序实体的文档。

    4>.env

       用于打印Go语言相关的环境信息。

    5>.fix

       用于修正指定代码的源码文件中包含的过时语法和代码调用。这使得我们在升级Go语言版本时,可以非常方便地同步升级程序。

    6>.fmt

       用于格式化指定代码包中的Go源码文件。实际上,它是通过执行“gofmt”命令来实现功能的。

    7>.generate

       用于识别指定代码中资源文件中的“go:generate”注释,并执行其携带的任意命令。该命令独立于Go语言标准的编译和安装体系。如果你有需要解析的“go:generate”注释,就单独运行它。这个命令非常有用,我产用它自动生成或改动Go源码文件。

    8>.get

       用于下载,编译并安装指定改动代码包及其依赖包。从我们自己的代码中转站或第三方代码库上自动拉取代码,就全靠它了。

    9>.install

      用于编译并安装指定的代码包及其依赖包。安装命令源码文件后,代码包所在的工作区目录的bin子目录,或者当前环境变量GOBIN指向的目录中会生成相应的可执行文件。安装源码文件后,会在代码包所在的工作目录的pkg子目录中生成相应的归档文件。

    10>.list

      用于显示指定代码包的细腻下,它可谓是代码包分析的一大便利工具。利用Go语言标准代码库代码包“text/template”中规定的模版语法,你可以非常灵活的控制输出信息。

    11>.run

      用于编译并运行指定的代码源码文件。当你想不生成可执行文件而直接运行命令源码文件时,就需要用到它。

    12>.test

      用于测试指定的代码包,前提是该代码包目录中必须存在测试源代码文件。 

    13>.tool

      用于运行Go语言的特殊工具。

    14>.vet

       用于检查指定代码包中的Go语言代码,并报告发现可疑代码问题。该命令提供了除编译之外的又一个程序检查方法,可用于找到程序中的潜在错误。

    15>.version

       用于显示当前安装的Go语言的版本信息以及计算环境。

      执行上述命令时,可以通过附加一些额外的标记来定制命令的执行过程。下面是一个比较通用的标记。

        -a:

          用于强行重新编译所有涉及的Go语言代码包(包括Go语言标准库中的代码包),即使他们已经是最新的了。该标记可以让我们有机会通过改动更底层的代码包做一些实验。

        -n:

          使命令仅打印其执行过程中用到的所有命令,而不真正执行它们。如果只想查看或验证命令的执行过程,而不想改变任何东西,使用它正合适。

        -race:

          用于检测并报告指定Go语言程序中存在的数据竞争问题。当用Go语言编写并发程序时,这是很重要的检测手段之一。

        -v:

          用于打印命令执行过程中涉及的代码包。这一定包括我们指定的目标代码包,并且有时还会包括该代码包直接或间接依赖的那些代码包。这会让你知道哪些代码包被命令处理过了。

        -work:

          用于打印命令执行时生成和使用的临时工作目录的名字,且命令执行完成后不删除它。这个目录下的文件可能会对你有用,也可以从侧面了解命令的执行过程。如果不添加此标记,那么临时工作目录会在命令执行完毕前删除。

        -x:

          使命令打印其执行过程中用到的所有命令,同时执行它们。

      我们可以把这些标记看作命令的特殊参数,他们都可以添加到命令名称和命令的真正参数中间。用于编译, 安装,运行和测试Go语言代码包或源码文件的命令都支持它们。上面提到了tool这个子命令,它用来运行一些特殊的Go语言工具。直接执行“go tool”命令,可以看到这些特殊工具。它们有的是其他Go标准命令的底层支持,有的规则是可以独当一面的利器。其中有两个工具值得特别介绍一下。

      pprof:

        用于以交互的方式访问一些性能概要文件。命令将会分析给定的概要文件,并根据要求提供高可读性的输出信息。这个工具可以分析的概要文件包括CPU概要文件,内存概要文件和程序阻塞概要文件。这些包含Go程序运行信息的概要文件,可以通过标准库代码runtime和runtime/pprof中的程序来生成。  

      trace:

        用于读取Go程序踪迹文件,并以图形化的方式展现出来。它能够让我们深入了解Go程序在运行过程中的内部情况。比如,当前进程中堆的大小及使用情况。又比如,程序中的多个goruntime是怎样调度的,以及它们在某个时刻被调度的原因。Go程序踪迹文件可以通过标准代码包“runtime/trace”和“net/http/pprof”中的程序来生成。

      上述的两个特殊的工具对Go程序调优非常有用。如果想探究程序运行的过程,或者想让程序跑的更快,更稳定,那么这两个工具是必知必会的。另外,这两个工具都受到“go test”命令的直接支持。因此你可以很方便地把它们融入到程序测试当中。

     

    五.问候程序

    /*
    #!/usr/bin/env gorun
    @author :yinzhengjie
    Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
    EMAIL:y1053419035@qq.com
    */
    
    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        //声明并初始化带缓冲带读取器,准备从标准输入读取内容
        inputReader := bufio.NewReader(os.Stdin)
    
        //给用户一个提示信息
        fmt.Println("Please input your name :")
    
        //以"\n"为分隔符读取一段内容
        input,err := inputReader.ReadString('\n')
    
        if err != nil {
            fmt.Printf("Found an error : %s\n",err)
        }else {
            //对input进行切片操作,去掉内容中最后一个字节"\n"
            input = input[:len(input)-1]
            fmt.Printf("Hello, %s!\n",input)
        }
    }

     

      请记住,学习一门编程语言最直接,最有效的途径就是多实验几次,多修改然后在运行一下看看效果。换句话说,多练习代码是学习编程最有效的途径!

      

    转载于:https://www.cnblogs.com/yinzhengjie/p/10141098.html

    展开全文
  • Go并发编程实战》读书笔记-语法概览  作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。  本篇博客我们会快速浏览一下Go的语法,内容涉及基本构成要素(比如标识符,关键字,子面量,操作...

                  《Go并发编程实战》读书笔记-语法概览

                                           作者:尹正杰 

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

     

      本篇博客我们会快速浏览一下Go的语法,内容涉及基本构成要素(比如标识符,关键字,子面量,操作符等)和基本类型(比如bool,byte,rune,int,string等),高级类型(比如数组,切片,字典,接口,结构体等)和流程控制语句(if,switch,for,defer等)。

     

    一.基本构成要素

      Go的语言符号又称为词法元素,共包括5类内容,即标识符(identifier),关键字(keyword),字面量(literal),分隔符(delimiter)和操作符(operator),它们可以组成各种表达式和语句,而后者都无需以分号结尾。

    1>.标识符(identifier)

      标识符可以表示程序实体,前者即为后者的名称。在一般情况下,同一个代码块中不允许出现同名的程序实体。使用不同代码包中的程序实体需要用到限定标识符,比如:“os.0_RDONLY”。

      另外,Go中还存在着一类特殊的标识符,叫作定义标识符,他们是在Go源码中声明的。这类标识符包括以下几种:

        第一:所有基本数据类型的名称;

        第二:接口类型error;

        第三:常量true,false和iota;

      所有内建函数的名称,即:append,cap,close,complex,copy,delete,imag,len,make,new,panic,print,println,real和recover。这里强调一下空标识符,它由一个下划线("_")表示,一般用在变量声明或代码包导入语句中。若在代码中存在一个变量“x”,但是却不存在任何对它的使用,则编译器会报错。如果在变量“x”的声明代码后添加这样一行代码:“ _ = x”就可以绕过编译器检查,使它不产生任何编译错误。这是因为这段代码确实用到了变量“x”,只不过它没有在变量“x”上进行任何操作,也没有将它复制给任何变量。空标识符就像一个垃圾桶。在相关初始化工作完成之后,操作对象就会被弃之不用。

    2>.关键字(keyword)

      关键字是指被编程语言保留的字符序列,编程人员不能把它们用作标识符。因此,关键字也称为保留字。

      Go的关键字可以分为3类,包括用于程序声明的关键字,用于程序实体声明和定义的关键字,以及用于程序流程控制的关键字,如下所示:

        程序声明: “improt”和“package”。

        程序实体声明和定义:“chan”,“const”,“func”,“interface”,“map”,“struct”,“type”和“var”。

        程序流程控制:“go”,“select”,“break”,“case”,“continue”,“default”,“defer”,“else”,“fallthrough”,“for”,“goto”,“if”,“range”,“return”和“switch”。

      Go的关键字共有25个,其中与并发编程有关的关键字又go,chan和select。

      这里特别说明一下关键字type的用途,即类型声明。我们可以使用它声明一个自定义的类型,如“type myString string”这里把名为myString的类型声明为string类型的一个别名类型。反过来说,string类型是myString类型的潜在类型。在看另一个例子,基本数据类型rune是int32类型的一个别名类型,int32类型就是rune的潜在类型。虽然类型及其潜在类型是不同的两个类型,但是他们的值可以进行类型转换,例如:string(myString("ABCD"))。这样的类型转换不会产生新值,几乎没声明代价。

      自定义的类型一般都会基于Go中的一个或多个预定义类型,就想上面的myString和string那样。如果为自定义类型关联若干方法(函数的变体),那么还可以让它成为某个或某些接口类型的实现类型。另外,还有一个比较特殊的类型,叫空接口。它的类型字面量是“interface{}”,在Go语言中,任何类型都是空接口类型的实现类型。

    3>.字面量(literal)

       简单来说,字面量就是值的一种标记法。但是,在Go中,字面量的含义要更加广泛一些。我们常常用到的字面量有以下三类:

        第一:用于表示基础类型值的各种字面量。这是最常用到的一类字面量,例如表示浮点数类型值的“12E-3”。

        第二:用于构造各种自定义的复合数据类型的类型字面量,例如下面定义了一个名称为Name的结构体类型:        

    type MyName struct {
        Id int
        Name string
    } 

        第三:用于表示复合数据类型的值的复合字面量,它可以用来构造struct(结构体),array(数组),slice(切片)和map(字典)类型的值。复合字面量一般有字面类型以及被花括号包裹的复合元素的列表组成。字面类型指的就是复合数据类型的名称。例如,下面的复合字面量构造了一个MyName 类型的值,其中MyName表示这个值的类型,紧随其后的就是由键值对表示的复合元素列表。

    package main
    
    type MyName struct {
        Id int
        Name string
    }
    
    
    
    func main() {
    
        yzj := MyName{
            Id:001,
            Name:"尹正杰",
        }
    
        println(yzj.Id)
        println(yzj.Name)
    
    }

    4>.操作符(operator)

     

    5>.表达式

     

     

    二.基本类型

     

    三.高级类型

     

    四.流程控制语句

     

    转载于:https://www.cnblogs.com/yinzhengjie/p/10171381.html

    展开全文
  • Go并发编程实战笔记

    2020-07-01 10:50:48
    一、Go语言的主要特征 1.开放源代码的通用计算机编程...8.Go语言的垃圾回收采用的是并发的标记清楚算法(Concurrent Mark and Sweep,CMS)。虽然是并发的操作,时间比串型操作短很多,但是还是会在垃圾回收期间停止所

    一、Go语言的主要特征

    1.开放源代码的通用计算机编程语言。

    2.静态类型、编译形的语言,语法趋于校本化。

    3.卓越的跨平台支持,无需移植代码。

    4.全自动的垃圾回收机制,无需开发者干预。

    5.原生的先进并发模型和机制。

    6.拥有函数式编程范式的特性,函数为一等代码块。

    7.无继承层次的轻量级面向对象编程范式。

    8.Go语言的垃圾回收采用的是并发的标记清除算法(Concurrent Mark and Sweep,CMS)。虽然是并发的操作,时间比串型操作短很多,但是还是会在垃圾回收期间停止所有用户程序的操作。

     

    二、包初始化

    1.在Go语言中,可以有专门的函数负责代码包初始化。这个函数需要无参数声明和结果声明,且名称必须位init。

    2.所有代码包初始化函数都会在main函数之前执行完成,而且只会执行一次。

    3.当前代码包中的所有全局变量的初始化会在代码包初始化函数执行完成。

    4.在同一个代码包中,可以存在多个代码包初始化函数。

    5.Go语言编译器不能保证同一代码包中的多个代码包初始化函数的执行顺序,如果确实需要,可以考虑使用Channel进行控制。

     

    三、语法与数据类型

    基本词法:

    1.Go语言源码文件必须是UTF-8编码格式的。

    标识符:

    2.当我们只想执行下某个代码包中的初始化函数,而不需要使用这个代码包中的任何程序实体的时候,可以这样编写导入语句:

    import _ "runtime/cgo"

    字面量:

    3.用于表示复合数据类型的值的复合字面量。(Struct、Array、Slice、Map)

    4.对复合字面量的每次求值都会导致一个新的值被创建。

    类型:

    5.Go语言中的类型可以分为静态类型和动态类型。

    6.静态类型是指在变量声明中示出的那个类型,绝大多数类型的变量都只拥有静态类型。

    7.动态类型代表了在运行时与该变量绑定在一起的值的实际类型,这个实际类型可以是实现了这个接口类型的任何类型。

    8.唯独接口类型既有静态类型,又有动态类型。

    9.接口类型的变量的动态类型可以在执行期间变化,因为所有实现了这个接口的类型的值都可以被赋给这个变量。但是,这个变量的静态类型永远只能是它声明时被指定的那个类型

    10.每一个类型都有一个潜在类型。如果这个类型是一个预定义类型(基本类型),或者是一个由类型字面量构造的复合类型,那么它的潜在类型就是它本身。如果一个类型并不属于上述情况,那么这个类型的潜在类型就是在类型声明中的那个类型的潜在类型。

    11.Go语言中rune可以看作是uint32类型的一个别名类型,其潜在类型就是uint32。

    12.一个类型的潜在类型具有可传递性。

    13.一个数组类型的潜在类型决定了在该类型的变量中可以存放哪一个类型的元素。

    14.如果除数是零,但被除数是浮点数类型或复合类型,不一定会panic。

    操作符:

    15.当多个逻辑操作符和操作数进行串联时,Go语言总是从左到右依次对他们求值。

    16.*被称为取值操作符,&被称为取址操作符。

    17.++和--是语句而不是表达式。*p++等同于(*p)++。

    表达式:

    18.数组切片的长度范围就是int类型可以表示的非负的取值范围。

    19.对于str[],如果str是字符串类型,则不能对str[0]进行赋值操作,因为字符串类型值是不能改变的。

    20.当字典类型a的值为nil时,a[x]并不会发生任何错误,但是对a[x]进行赋值时却会引起恐慌。

    21.在调用可变参数函数时,会创建一个类型相同的切片,用于存放实际参数。

    22.我们可以直接把一个元素类型位T的切片类型赋值给...T类型的可变参数。格式:test([]int{1,2,3}...)

    23.也可以将...T类型赋值给切片。

    func test(i ...int){
       ints := []int{}
       ints = i
       fmt.Println(ints)
    }

    数据类型:

    24.原生字符串是被两个反引号包括的` 。原生字符串中的回车符会被编译器移除。

    25.数组的长度是数组类型的一部分,只要类型声明中的数组长度不同,即使两个数组的元素类型相同,他们也还是不同的类型。

    26.数组赋值根据元素值递增规则推算。

    i := [3]int{1:1,3}
    fmt.Println(i)
    [0 1 3]

    27.指定的元素索引值不能与其他元素索引值重复,无论是索引值是显式对应还是隐式对应的。

    s := [3]string{0:"a","b",1:"c"}

    28.可以不显式的指定数组长度,用...填充,让编译器为我们计算所含元素个数确定长度。

    s := [...]string{"a","b"}

    29.数组索引不在范围内,在编译期间就会造成编译错误

    30.切片是对数组的一种包装形式。

    31.切片长度需要控制在intleasing所能表示的非负值范围之内。

    32.一个切片值总会持有一个对某个数组值的引用。事实上,一个切片值一旦被初始化,就会与一个包含了其中元素值的数组值相关联。

    33.多个切片值可能会共用同一个底层数组。

    34.对作为底层数组的数组值中的元素值的改变,也会体现到引用该底层数组且包含该元素值的所有切片值上。

    35.一个切片值的容量是从其中的指针指向的那个元素值,到底层数组的最后一个元素的计数值。

    36.如果切片长度超过了原数组的长度,将有一个新的数组被创建并初始化。

    37.为切片设置容量。1:保证只能修改底层数组指定索引内的值。2:避免长度超过底层数组而创建新数组,之前的数组修改入口丢失。

    38.map的键必须是可比较的,也就是说,键的值必须可以作为比较操作符==和!=的操作数。

    39.与指针类型和切片类型一样,字典类型是一个引用类型。与切片值相同,一个字典值总是会持有一个针对某个底层数据结构值的引用。

    40.Go语言中只有“传值”而没有”传引用“。函数内部对参数值的修改是否会在函数外体现,只取决于被改变的值的类型是值类型还是引用

         类型。

    41.一个值为nil的字典相当于长度为0的空字典,对其进行读操作不会引起任何错误,但是对它进行写操作会引起恐慌。

    42.delete函数不存在返回值,也不会在运行时发生错误或恐慌。

    43.值方法包含了与它关联的所有值方法,指针方法却包含了所有值方法和指针方法。

    44.在指针方法中一定能够改变接受者的值,而在值方法中,对接收者的值的改变对于该方法之外一般是无效的。

    45.接收者类型如果是引用类型的别名类型,那么在该类型的值方法中对该值的改变也是对外有效的。

    46.切片类型和字典类型都是引用类型。除此之外,通道类型也是引用类型

    47.Go语言对接口的实现是非入侵式的。要想实现一个接口,只需要实现其中的所有方法集合即可,而不需要在数据类型上添加任何标记。

    48.一个接口类型只接收其他接口类型的嵌入,不能嵌入自身。这包括直接嵌入和间接嵌入。

    49.一个接口类型可以被任意数量的数据类型实现,一个数据类型也可以同时实现多个接口类型。

    50.任何较浅层次的嵌入类型的字段或方法都会隐藏较深层次的嵌入类型包含的同名字段或方法。

    51.这种隐藏是可以交叉进行的,即字段可以隐藏方法,方法也可以隐藏字段。

    52.如果在同一嵌入层次中的两个嵌入类型拥有同名字段或方法,那么涉及他们的表达式将造成编译错误。

    53.不论从修改最小化还是维护性方面来看,扩展一个接口类型远远要比直接对这个接口类型修改好得多。

    54.new函数用于为值分配内存。它并不会初始化分配到的内存,而只会清零它。

    55.调用表达式new(T)被求值时,所做的是为T类型的新值分配并清零一块内存空间,然后将这块空间的地址返回,即*T。

    56.new会立即得到一个可用的值的指针值,而不需要再做额外的初始化。

    57.make函数只能被用于创建切片类型、字典类型和通道类型的值。并返回一个已经被初始化(非零值)的对应类型的值。

    58.这么做的原因是因为它们都是引用类型,在它们的每一个值的内部都保持着一个对某个底层数据结构值的引用。如果不对它们的值

         进行初始化,那么其中的这种引用关系是不会被建立起来的,同时相关的内部值也会不正确。在这种情况下,该类型的值就不能被

         使用,因为它们是不完整的,还处于未就绪的状态。

    59.切片类型、字典类型和通道类型的零值都是nil,而不是那个未被初始化的值。当new这三种类型时,得到的是指向空值nil的指针值。

    60.内建函数make与new接收的函数也有所不同。对于切片来说,我们可以把新值的长度和容量传递给make函数,如果不指定容量,将与

         长度一致。

    61.make函数只能被应用在引用类型的值的创建上。并且,它的结果是第一个参数所代表的类型的值,而不是指向这个值的指针值。

    数据的使用:

    62.字面量可以用于初始化几乎所有的Go语言数据类型的值,除了接口和通道类型。接口类型没有值,通道类型只能使用make函数创建。

    63.赋值语句的执行分两个阶段。第一个阶段,表达式按顺序被求值。(赋值操作符左边的索引表达式-->取址表达式的操作数-->

         赋值操作符右边表达式);第二个阶段,赋值会以从左到右的顺序执行。

    64.平行赋值永远是从左向右执行的,即使靠右的赋值发生了恐慌,它左边的赋值依然会生效。

    65.操作数为无类型常量的移位操作的结果总会是一个无类型的整数常量。

    66.在同一条常量声明语句中,iota代表的整数常量是否递增取决于是否又有一个常量声明包含了它,而不是它是否又在常量声明中出现

         了一次。

    const (
       e,f = iota,iota
       g,h
       i,j
    )
    
    func main() {
       fmt.Println(e,f,g,h,i,j)
    }

    输出0 0 1 1 2 2

    67.我们可以利用_跳过iota表示的递增序列中的某个值或某些值。

    68.对于两个结构体来说,如果它们之中的字段声明的数量是相同的,并且在对应位置上的字段具有相同的字段名称和恒等的数据类型,

         那么这两个结构体数据类型就是恒等的。如果一个结构体含匿名字段,那么另一个结构体的对应字段也必须匿名;结构体声明中的

         字段标签(如`json:`)也应该作为恒等判断的依据。

    69.对于指针,如果两个指针的基本类型恒等(也就是它们指向的那个类型),那么它们就是恒等的。

    70.函数类型的恒等判断并不会以参数和结果的名称为依据,而只关心它们的数量,类型和位置。

    71.参数中切片类型和可变长度不恒等。

    72.对于两个接口类型,如果它们拥有相同的方法集合,那么它们就是恒等的。方法声明的顺序是无关紧要的。

    73.如果两个数据类型在不同的代码包中,即使满足上述相关规则也不是恒等的。

    74.分别指向两个不同的、大小为零的变量的指针有可能是相等的也可能是不相等的。

    75.大小为零的变量是指无任何字段的结构体值或无任何元素的数组值。大小为零的变量可能会有相同的内存地址,因为它们在本质上

         很可能是没有区别的。

    76.通道类型具有可比性。如果两个通道类型值的元素类型和缓冲区大小都一致,那么就可以被判定为相等。另外,如果两个通道类型的

         变量的值都是nil,那么它们也是相等的。

    func main() {
       var c1 chan int
       var c2 chan int
       c3 := new(chan int)
       c4 := make(chan int)
       c5 := make(chan int,1)
       c6 := make(chan int,1)
       c7 := make(chan int)
    
       fmt.Println(c1 == c2)
       fmt.Println(c1 == *c3)
       fmt.Println(c1 == c4)
       fmt.Println(c5 == c6)
       fmt.Println(c5 == c7)
       fmt.Println(c4 == c7)
    }

    输出:

    true
    true
    false
    false
    false
    false

    77.接口类型值具有可比性。如果两个接口类型值拥有相等的动态类型和相同的动态值,那么就可以判定它们是相等的。

         如果我们有一个接口类型的变量,那么在这个变量中就只能存储实现了该接口类型的类型值。我们把存储在该变量中的那个值的类型

         叫做该变量的动态类型,而把这个值叫做该变量的动态值。另外,如果两个接口类型的变量的值都是空值,那么它们也是相等的。

    78.非接口类型X的值x可以与接口类型T的值t判断相等,当且仅当接口类型T具有可比性且类型X是接口类型T的实现类型。如果值t的动态

         类型与类型X恒等并且值t的动态值与值x相等,那么就可以说值t和值x就是相等的。除上述情况之外,不同数据类型的值相比较,会造

         成编译错误。

    79.如果一个结构体类型中的所有字段都具有可比性,那么这个结构体类型就具有可比性。如果两个结构体对应字段相等,那么两个结构体

         就是相等的。切片类型的值不具有可比性。

    80.数组类型具有可比性,当且仅当元素类型的值具有可比性。

    81.一个例外情况是,在判断两个具有相同接口类型的值是否相等时,如果它们的动态类型不具有可比性就会引发恐慌。

    82.切片类型、字典类型和函数类型的值是不具有可比性的。然而,作为特例,这些值可以与空值nil进行判等。

    83.对于非常量x,如果它能够被转换为类型T的值,那么它肯定符合下列情况的一种。

         (1).值x可以被赋给类型T的变量。

         (2).值x的类型和类型T的潜在类型是相等的。

         (3).值x的类型和类型T都是未命名的指针类型,并且它们的基本类型(指向的那个值的类型)的潜在类型是相等的。

         (4).值x的类型和类型T都是整数类型或浮点数类型。

         (5).值x的类型和类型T都是复数类型。

         (6).值x是一个整数类型值或是一个元素类型为byte或rune的切片类型值,且T是一个string类型。

         (7).值x是一个string类型值,且T是一个元素类型为byte或rune的切片类型。

    84.byte类型值和rune类型值都属于整数值的一种。

    85.内建函数close只接收通道类型的值作为参数。

    86.调用这个close函数之后,会使作为参数的通道无法再接受任何元素值。若试图关闭一个仅能接收元素值的通道,则会造成编译错误。

    87.在通道关闭之后,再向它发送元素值或再次关闭它的时候,将会引发运行时恐慌。关闭为nil的通道也会引发恐慌。

    四、流程控制方法

    88.if语句和switch语句都可以接受一个可选的初始化子语句。

    89.switch分为表达式switch和类型switch。

    90.如果在switch语句中没有显式的switch表达式,那么true将作为switch表达式,所有的case表达式的结果都应是布尔类型。

    func main() {
       i := 2
    
       switch {
       
          case i < 1:
             i++
          case i > 1:
             i--
          default:
             fmt.Println("default")
       }
    }

    91.fallthrough语句只能够作为case语句中的最后一条语句,更重要的是,该关键字不能出现在最后一个case中。

    92.fallthrough不能出现在类型switch中。

    func main() {
       s := "中国"
       var inter interface{}
       inter = s
       
       switch i := inter.(type) {
       case int:
          fmt.Println("i==int,%T\n",i)
       case string:
          fmt.Printf("i==string,%T\n",i)
       default:
          fmt.Println("default")
       }
    }

    输出:

    i==string,string

    93.一般情况下,range表达式只会在迭代开始前被求值一次。

    94.对于一个字符串值来说,在一个连续的迭代之上产出的索引值(第一迭代值)既是某一Unicode代码点(与rune类型值一一对应)的

         UTF-8编码值中的第一个字节在与其所属的[]byte类型上的索引值。

    s := "Golang爱好者"
    for index,str := range s {
       fmt.Println()
       fmt.Println("index-->" + strconv.Itoa(index) + "str:" + string(str))
    }

    输出:

    index-->0str:G

    index-->1str:o

    index-->2str:l

    index-->3str:a

    index-->4str:n

    index-->5str:g

    index-->6str:爱

    index-->9str:好

    index-->12str:者

    95.对于一个字典值来说,它的迭代顺序是不固定的。如果字典中的键值对在还没有被迭代到的时候就被删除了,那么相应的迭代值就不会

         产出。另一方面,如果我们在字典值被迭代的过程中添加新的键值对,那么相应的迭代值是否会产出是不确定的。

    func main() {
    	testMap := map[int]string{
    		1:"a",
    		2:"b",
    		3:"c",
    	}
    	go func() {
    		delete(testMap,1)
    		testMap[5] = "e"
    		delete(testMap,2)
    		testMap[4] = "d"
    		delete(testMap,3)
    		testMap[6] = "f"
    	}()
    	for index,value := range testMap {
    		fmt.Println("index:"+strconv.Itoa(index)+"   value:"+value)
    	}
    }

    96.如果range的表达式求值结果是一个通道类型,那么只会产出一个迭代值。

    97.使用go语言for语句写出一个反转切片中左右数据值的方法。

    func main() {
    	list := []int{0,1,2,3,4,5,6,7,8,9}
    
    	for i,j := 0,len(list);i < j/2; i++ {
    		list[i],list[j - i - 1] = list[j - i - 1],list[i]
    	}
    	fmt.Println(list)
    }

    98.for子句的前置初始化子句和后置子句只能是单一语句而不能是多个语句,但是能够使用平行赋值。

    99.当外围函数的函数体中的return语句被执行的时候,只有在该函数中的所有defer语句都被执行完毕之后才会正真的返回。

    100.当在外围函数中有运行时恐慌发生时,只有该函数中所有的defer语句都执行完毕之后,运行时恐慌才会真正的扩散至该函数调用方。

    101.在recode函数执行之触begin函数就已经被调用了,而end函数的调用却是在recode函数结束的前一刻。

    func begin(funcName string) string {
    	fmt.Println("Begin function recode")
    	return funcName
    }
    
    func end(funcName string) string {
    	fmt.Println("End function recode")
    	return funcName
    }
    
    func recode()  {
    	defer end(begin("recode"))
    	fmt.Println("In function recode")
    }
    
    func main() {
    	recode()
    }
    
    
    Begin function recode
    In function recode
    End function recode

    102.这样做可以避免参数值在延迟函数被真正调用之前再次发生改变而给该函数的执行造成影响之外,还是出于同一条defer语句可能会

           被多次执行考虑。

    func main() {
    	for i := 0; i < 5 ; i++ {
    		defer fmt.Println(i)
    	}
    }
    
    
    
    4
    3
    2
    1
    0
    

    103.Go语言会把代入参数值之后的调用表达式另行存储。

    104.每当Go语言把已代入参数值的延迟函数调用表达式另行存储之后,还会把它追加到一个专门为当前外围函数存储延迟函数调用表达式

           的列表当中。这个列表总是LIFO(后进先出)的。

    105.在defer语句被执行的时候传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在那时求值。

    func main() {
    	for i := 0; i < 5 ; i++ {
    		defer func() {
    			fmt.Println(i)
    		}()
    	}
    }
    
    
    5
    5
    5
    5
    5

    106.延迟函数中的代码是可以对命名结果值进行访问和修改的,其次,虽然延迟函数中可以包含结果声明,但是其返回结果会在它被执行

           完毕时丢弃。

    107.除了errors.New函数外,另一个可以生成error值的方法是调用fmt包的Errorf函数。其内部的创建和初始化也是通过errors.New函数

           来完成的。

    // Sprintf formats according to a format specifier and returns the resulting string.
    func Sprintf(format string, a ...interface{}) string {
    	p := newPrinter()
    	p.doPrintf(format, a)
    	s := string(p.buf)
    	p.free()
    	return s
    }
    
    // Errorf formats according to a format specifier and returns the string
    // as a value that satisfies error.
    func Errorf(format string, a ...interface{}) error {
    	return errors.New(Sprintf(format, a...))
    }

    108.如果运行时恐慌重新被引发,在调用栈信息中,不但会包含该恐慌首次引发时的调用轨迹和位置,还会包含重新引发该恐慌的位置。

    109.在java语言中java.util.HashSet类就是用java.util.HashMap类作为底层支持的。

    110.Go语言怎样生成interface{}类型的hash值?

           当我们把一个interface{}类型值作为键添加到一个字典值的时候,Go语言会先获取这个值的实际类型(动态类型),然后再使用对应的

           hash函数对该值进行hash计算。在之后的键查找过程中也会存在这样的hash计算。

    111.sort.Sort函数使用的排序算法是一种由三项切分的快速排序、堆排序、插入排序组成的混合算法。不保证稳定性。

    112.虽然快速排序是最快的通用排序算法,但在元素值很少的情况下,它比插入排序要慢一些。

           而堆排序的空间复杂度是常数级别的,且它的时间复杂度在大多数情况下只略逊于其它两种排序算法。

           所以在快速排序中的递归达到一定深度时,切换至堆排序来节省空间是值得的。

    五、程序测试和文档

    113.测试文件以xxx_test.go命名,函数以TestXxx命名,入参可使用 t *testing.T 类型的参数。

    114.测试命令 go test:

           -run标记:-run标记值是一个正则表达式,名称与此表达式匹配的函数才会被执行。

           -timeout:为程序运行设置时间,如果超时将会造成恐慌。

           -short:告诉程序尽量缩短运行时间,具体怎样缩短,由测试函数自己实现。(testing.Short函数会返回布尔型的值。)

           -parallel:设置允许并发执行的功能测试函数的最大数量。先决条件:在功能测试函数开始处加入代码t.Parallel()。

                            -parallel标记是通过代码库runtime.GOMAXPROCS()函数设置的。   

    115.基准测试的函数以BenchmarkXxx命名,入参可使用 b *testing.B 类型的参数。

    116.与定时器相关的函数有三个,b.StartTimer、b.StopTimer、b.ResetTimer。

           b.StartTimer方法意味着开始对当前测试函数的执行进行计时,它总会在开始执行测试函数时自动执行。因此,这个方法暴露

           的意义在于:计时器在被停止之后重新启动。相应的,b.StopTimer方法可使计时器停止。

    117.b.ResetTimer会重置当前的计时器,也就是说,把该函数的执行时间重置为0.

    118.b.setBytes方法被用于记录在单次操作中被处理的字节数量。

    六、并发编程综述

    119.并发程序是指可以被同时发起执行的程序,而并行程序则是被设计成可以在并行的硬件上执行的并发程序。

           换句话说,并发程序代表了所有可以实现真正的或者可能的并发行为的程序。并行程序时并发程序的一种。

    120.并发程序内部的交互方式:同步(异步)和传递数据。

    121.多进程通讯(IPC)从处理方式上看分为三类:基于通讯的IPC方法、基于信号的IPC方法、基于同步的IPC方法

           基于通讯的IPC:

                   以数据传送为手段的IPC方法:

                           管道:传送字节流。

                           消息队列:传送结构化的消息对象。

                   以共享内存为手段的IPC方法:

                           以共享内存区为代表,是最快的一种IPC方法。

           基于信号的IPC方法:

                   操作系统的信号机制,唯一一种异步的IPC方法。

          基于同步的IPC方法:

                   最重要的就是信号灯。

    122.我们通常把一个程序的执行成为一个进程。反过来讲,进程被用于描述程序的执行过程。因此,程序与进程是一对相依赖的概念。

           它们分别描述了一个程序的静态形式和动态特征。进程还是操作系统进行资源分配的基本单位。

    123.在Unix/Linux操作系统中,每一个进程都有父进程。所有的进程共同组成了一个树状结构。内核启动进程作为进程树的根并负责系统

           的初始化工作。它是所有进程的祖先,它的父进程是它本身。如果某一个进程先于它的子进程结束,那么这些子进程将会被内核启动

           进程”收养“,成为它的直接子进程。

    124.为了管理进程,内核会将每个进程的信息记录在进程描述符中。进程ID(常被称为PID)是进程在操作系统中的唯一标示。

           进程ID为1的进程就是内核启动进程,新被创建的进程ID是上一个递增的结果。进程ID可以被重复使用。

           当进程ID达到最大限值时,内核会从头开始寻找闲置的ID。

           进程描述符中还会包含当前进程的父ID(PPID)。

           Go语言查看当前进程PID和PPID的API:

                   pid := os.Getpid()

                   ppid := os.Getppid()

           PID并不传达与进程有关的任何信息,它只是用来唯一标识进程的数字而已。

    125.在Linux操作系统中,进程可能的状态一共有六个(暂停状态和调试状态十分相似,但是也可以看成两种状态,所以可以说7种状态)。

           可运行状态、可中断的睡眠状态、不可中断的睡眠状态、暂停状态或跟踪状态、僵尸状态和退出状态。

          

    126.Linux操作系统将物理内存分为内核空间和用户空间。用户进程都存在于用户空间,用户空间不能与硬件进行交互。

           内核可以与硬件交互,但是内核生存在内核空间。用户无法直接访问内核空间。

           32位的计算机可以有效标识2的32次方个内存单元,64位则可以标识2的64次方个。

    127.这里所说的地址并非物理内存中的真实地址,它们被称为虚拟地址。

           虚拟内存的最大容量与实际可用的物理内存的大小是无关的。

           内核和cpu会负责维护虚拟内存与物理内存之间的映射关系。

           内核会为每个用户进程分配的是虚拟内存而不是物理内存。

           进程之间的虚拟内存几乎是彼此独立互不干扰的。

    128.内核会暴露出一些接口,这些接口是用户进程使用内核进程(包括操纵计算机硬件)的唯一手段。

           当用户进程发出一个系统调用的时候,内核会把CPU从用户态切换到内核态。

           CPU在内核状态下才有权访问内核空间。

    129.我们把执行过程中不能被中断的操作称为原子操作,而把只能被串行化的访问或执行的某个资源或某段代码成为临界区。

           所有的系统调用都属于原子操作。

           原子操作不能被中断,临界区只要保证一个访问者在临界区的时候其它访问者不会被放进来就可以了。它们的强度是不同的。

           保证只有一个进程或线程在临界区之内的这种做法被称为--互斥,实现互斥的方法必须确保“排他原则”。

    130.管道是一种半双工(单向的)的通讯方式,它只能用于父进程与子进程以及同祖先的子进程之间的通讯。

           我们在使用Shell命令的时候经常用到管道,Go语言是支持管道的。

    cmd0 := exec.Command("echo","-n","Hello world")

           使用exec.Command类型的Start方法可以启动一个命令,

    if err := cmd0.Start(); err != nil{
    		println(err)
    	}

           但是为了创建一个能够获取此命令的输出管道,我们需要在上面这条if语句之前加入下面的代码:

    stdout0,err := cmd0.StdoutPipe()
    	if err != nil {
    		return
    	}

           变量cmd0的StdoutPipe方法会返回一个输出管道,它的类型是io.ReadCloser。之后我们可以调用stdout0的read方法获取命令输出。

    outputBuf0 := bufio.NewReader(stdout0)
    	output0,_,err := outputBuf0.ReadLine()
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(string(output0))

           bufio.NewReader方法会返回一个bufio.Reader类型的值,被称为缓冲读取器。默认情况下,该读取器会携带一个长度为4096的缓冲

                   区,缓冲区代表了我们一次可读取的最大数量。

    131.管道可以把一个命令的输出做为另一个命令的输入。

    cmd1 := exec.Command("ps","aux")
    	cmd2 := exec.Command("grep","apipe")
    	stdout1,err := cmd1.StdoutPipe()
    	if err != nil {
    		return
    	}
    	if err := cmd1.Start(); err != nil{
    		fmt.Println(err)
    		return
    	}
    
    	outputBuf1 := bufio.NewReader(stdout1)
    	output1,_,err := outputBuf1.ReadLine()
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(string(output1))
    
    	stdin2,err := cmd2.StdinPipe()
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    
    	outputBuf1.WriteTo(stdin2)

           我们通过StdinPipe方法在cmd2上创建输入管道,并把与cmd1连接的输出管道的数据全部写入到输入管道中。

           我们还需要启动cmd2并关闭与它连接的管道,以完成数据传递。

    var output2 bytes.Buffer
    	cmd2.Stdout = &output2
    	if err := cmd2.Start(); err != nil{
    		fmt.Println(err)
    		return
    	}
    	err = stdin2.Close()
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(output2)

           为了获取到cmd2的所有输出内容,我们需要等到它运行结束,再去查看缓冲区内容。

           方法Wait会一直阻塞到其所属命令完全运行结束为止。

    if err := cmd2.Wait(); err != nil {
    		fmt.Println(err)
    		return
    	}

    132.上面所说的管道都属于匿名管道,与此相对的是命名管道。与匿名管道不同的是,任何进程都可以通过命名管道交换数据。实际上,

           命名管道以文件的形式存在于文件系统中。使用它的方法与使用文件很类似,Linux操作系统支持使用Shell命令创建和使用管道。

           先试用命令mkfifo在当前目录创建了一个命名管道myfifo1,然后使用这个命名管道和tee命令把src.log文件中的内容写到dst.log中。

    133.我们可以使用命名管道运输数据,实现数据的过滤和转换,以及管道的多路复用等功能。

           命名管道默认是阻塞式的,只有在对这个命名管道读操作和写操作都准备就绪之后,数据才会开始流转。

           相对于匿名管道,命名管道最大的优势就是通讯双方可以毫不相关。并且我们可以使用它建立非线性的连接以实现数据的多路复用。

           但命名管道仍然是单向的,由于我们可以在命名管道之上实现多路复用,所以有时候也需要考虑多个进程同时向命名管道写数据的

           情况下的原子操作性问题。

    134.cmd对象的三个方法StdoutPipe、StdinPipe和StderrPipe分别代表标准输出、标准输入和错误输入。

           https://www.jianshu.com/p/d5ea4a8acfb9

    135.Go语言提供了创建命名管道的方法。

    reader,writer,err := os.Pipe()

           管道都是单向的,我们不能反过来使用reader和writer。另外,无论在哪一方调用Close方法,都不会影响另一端的读取或写入操作。

           实际上,exec.Cmd类型之上调用StdinPipe和StdoutPipe方法后得到的输入管道或输出管道都是通过os.Pipe函数生成的,只不过,在

           这两个方法内部又对生成的管道做了少许处理。输入管道的输出端会在所属命令启动后就立即被关闭,而输入端则会在所属命令运行

           结束后关闭。而输出管道两端的自动关闭时机与前面刚好相反。

           有些命令会等到输入管道被关闭之后才会结束运行,所以,我们就需要在数据被读取之后尽早的手动关闭输入管道。

    136.由于输出管道实际上也是有os.Pipe函数生成的,所以我们在使用某个exec.Cmd类型值上的输出管道的时候需要有所注意。

           我们不能在调读完输出管道中的全部数据之前调用该值的Wait方法。

           只要我们建立了对应的输出管道,就不能使用Run方法来启动该命令,而应该使用Start方法。

    137.通过os.Pipe函数生成的管道在底层是由操作系统管道支持的,命名管道可以被多路复用,操作系统提供的管道不提供原子性操作。

           为此Go语言标准库代码包io中提供了一个被存于内存中、有原子性操作保证的管道(内存管道)。

    pipeReader,pipeWriter := io.Pipe()

           为了避免使用者对管道的反向使用,在*PipeReader类型的值上我们只能使用Read方法读取数据,*PipeWriter同理。

           我们在使用Close方法关闭管道的某一端之后,另一端在读取或写入数据时会得到一个可预定义的error值。

    138.内存管道的内部是充分使用sync代码包中提供的API来从根本上保证操作的原子性的,我们可以在它之上放心的写入和读取数据。

           由于这种管道不是基于文件系统,并没有作为中介的缓冲区,所以它传递数据只复制一次。

    139.操作系统信号本质是用软件来模拟硬件的中断机制。

    140.Linux操作系统支持的信号又62种。编号1-31的信号属于标准信号(不可靠信号),编号34-64的信号属于实时信号(可靠信号)。

           对于同一个进程来说,每种标准信号只会被记录并处理一次。如果发送给某一进程的标准信号的种类有多个,那么它们的处理顺序是

           完全不确定的。而实时信号解决了标准信号的这两个问题。

    141.信号的来源有键盘输入、硬件故障,系统函数调用和软件中的非法运算。进程响应信号的方式有3种:忽略、捕捉和执行默认操作。

           Linux系统对每一个标准信号都有默认的操作方式。针对不同种类的标准信号,其默认的操作方式一定会是以下操作中的一个:

           终止进程、忽略该信号、终止进程并保存内存信息、停止进程、若进程已停止就恢复。

    142.Go命令会对其中的一些以键盘输入为来源的标准信号做出响应,这是由于go命令使用了在标准代码包os/signal中的被用于信号处理的

           API。更具体的讲,go命令指定了需要被处理的信号并使用了通道类型的变量来监听信号的到来。

    type Signal interface {
    	String() string
    	Signal() // to distinguish from other Stringers
    }

           所有此接口类型的实现类型值都应该代表一个操作系统信号,理所当然,每一个操作系统信号都是需要操作系统支持的。

           在Go语言标准库代码包syscall中,已经为不同操作系统的所支持的标准信号都生成了一个相应同名常量(即信号常量)。

           这些信号常量的类型都是syscall.Signal,syscall.Signal是os.Signal接口类型的一个实现类型,同时也是int类型的别名类型。这就

           意味着每个信号常量都隐含着一个整数值,而信号常量的整数值与它代表的信号在所属操作系统中的编号是一致的。

    143.代码包os/signal中的Notify用来把操作系统发给当前进程的指定信号通知给该函数的调用方。

    func Notify(c chan<- os.Signal, sig ...os.Signal)

           signal.Notify函数会把当前进程接收到指定信号放入参数c代表的通道类型值中。这样,调用方代码就可以从这个signal接收通道中按

           顺序的获取到操作系统发来的信号并进行相应的处理了。

           函数signal.Notify的第二个参数是一个可变长参数。参数sig代表的参数值是我们希望自行处理的所有信号。

    sigRrcv := make(chan<- os.Signal,1)
    sigs := []os.Signal{syscall.SIGINT,syscall.SIGQUIT}
    signal.Notify(sigRrcv,sigs...)
    for sig := range sigs {
        fmt.Println(sig)
    }

           在sigRrcv代表的通道类型值被关闭后,for会立即退出执行。

           signal处理程序在向signal接收通道发送值的时候,并不会因为通道已满而产生阻塞。所以signal.Notify函数的调用方必须保证接收

           通道有足够的空间缓存并传递接收到的信号。我们可以创建一个足够长的signal通道,但是更好的解决办法是,只创建一个长度为1

           的signal通道,并且时刻准备从该通道中接收信号。

    144.如果当前进程接收到了一个我们不想自行处理的信号,则执行操作系统默认操作。所以,如果我们指定了想要自行处理的信号,但又

           没有在接收到信号时执行必要的处理动作,就相当于使当前进程忽略这些信号。

    145.在类Unix操作系统下,有两种信号不能被自行处理也不会被忽略。它们是:SIGKILL和SIGSTOP。

    146.对于其它信号,我们除了能够自行处理他们之外,还可以在之后的任意时刻恢复针对它们的系统默认操作。这需要使用到os/signal

           包中的Stop函数。

    func Stop(c chan<- os.Signal)

           函数signal.Stop会取消掉在之前调用signal.Notify函数的时候告知signal处理程序需要自行处理若干信号的行为。只有我们把当初出

           传给signal.Notify函数的那个signal接收通道作为调用signal.Stop函数时的参数值,才能取消掉之前的行为,否则将不会起任何作用。

           这里存在一个副作用,之前用于从signal通道接收信号值的for语句将一直被阻塞。为了消除这种副作用,我们可以调用signal.Stop

           函数之后使用内建函数close关闭该signal接收通道。

    signal.Stop(sigRrcv)
    close(sigRrcv)

           signal接收通道被关闭后,被用于接收信号的for语句就会退出执行。

    147.很多时候,我们只想取消部分信号的自行处理行为,我们只需再次调用signal.Notify函数,并重新设定与其参数sig绑定的值,只要

           作为第一个参数的signal接收通道相同就可以。

           如果signal接收通道的值不同,那么signal处理程序会视为这两次调用毫不相干,它会分别看待这两次调用时所设定的信号的集合。

    148.在signal处理程序内部,存在一个包级私有的字典(信号集合字典)。该信号集合字典被用于存放以signal接收通道为键,以信号集合的

           变体为元素的键值对。当我们调用signal.Notify函数的时候,signal处理程序就会在信号集合字典中查找对应的键值对,如果不存在就

           添加,否则更新。当我们调用signal.Stop函数的时候,signal处理程序会删除相应的键值对。

           当接收到一个发送给当前进程且已被标识为应用程序想自行处理的操作系统信号时,signal处理程序会对它进行封装,然后遍历该map

           查看它们的元素是否包含该信号,如果包含会立即发送给作为键的signal接收通道。

    149.我们可以编写一个向进程发送信号的程序,主要依靠结构体类型os.Process和相关的函数和方法。

           我们可以使用os.StartProcess函数启动一个进程,或者使用os.FindProcess函数查找一个进程。这两个函数都会返回一个

           os.*Processde的值(进程值)和一个error值。然后我们可以调用进程值的Signal方法来向该进程发送信号。

    150.利用signal可以在进程被终止前释放所持的系统资源和持久化一些重要数据,还可以在当前进程中有效的控制其它相关进程的状态。

    151.信号和管道都被称为基础的IPC方法。但是管道并不提供原子性操作,Go语言的标准库API也没有附加这种保证。

    152.Socket,常被译为套接字。也是一种IPC方法,它是通过网络连接来使两个或更多的进程建立通讯并相互传递数据的。

     

     

     

     

     

    展开全文
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 我们在本章前面的部分中对Go语言提供的各种传统同步工具和方法进行了逐一的介绍。在本节,我们将运用它们来...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 我们在第6章讲多线程编程的时候详细说明过条件变量的概念、原理和适用场景。因此,我们在本小节仅对sync代码包...
  • 文章作者:郝林(《Go并发编程实战 (第2版)》作者) 最终来了!经过出版社的各位编辑、校对、排版伙伴与我的N轮PK和共同努力,《Go并发编程实战》第2版的全部内容最终全然确定,并于2017年3月24日交付印刷!当然...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 我们在第6章多次提到过sync.WaitGroup类型和它的方法。sync.WaitGroup类型的值也是开箱即用的。例如,在声明 ...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 我们已经知道,原子操作即是进行过程中不能被中断的操作。也就是说,针对某个值的原子操作在被进行的过程当中...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 本章要讲解的是sync.Pool类型。我们可以把sync.Pool类型值看作是存放可被重复使用的值的容器。此类容器是自动...
  • Go并发编程实战 第2版 目录 第1章 初识Go语言 11.1 语言特性 11.2 安装和设置 21.3 工程结构 31.3.1 工作区 31.3.2 GOPATH 41.3.3 源码文件 51.3.4 代码包 81.4 标准命令简述 ...
  • 经过出版社的各位编辑、校对、排版伙伴与我的N轮PK和共同努力,《Go并发编程实战》第2版的所有内容终于完全确定,并于2017年3月24日交付印刷!当然,印刷也经历了若干流程,以尽量把出错概率压到最低。从现在开始,...
  • 声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。 现在,让我们再次聚焦到sync代码包。除了我们介绍过的互斥锁、读写锁和条件变量,该代码包还为我们提供了几个...
  • 其中对go并发原理部分讲的不错,可以着重看一下! 本书在图灵社区的主页: http://www.ituring.com.cn/book/1950 在Github上的主页: https://github.com/gopcp 在这两个地方,你都可以获得全部示例代码和勘误...
  • 导图呐就两张,且没有什么参考价值
  • 第1章 初识Go语言  1.1 语言特性  1.2 安装和设置  1.3 工程构造  1.3.1 工作区  1.3.2 GOPATH  1.3.3 源码文件 package main import ( "fmt" "runtime" ) var m = map[int]string{1:"A...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 207
精华内容 82
关键字:

go并发编程实战