go语言全局变量命名规范,go语言定义变量

用数组的方式实现大小写英文字符串的密码转换,大写字母加4,小写字母减4,后面的再转回去,如W加密为A,X

所用到的都是ASP读写数据库操作.

创新互联建站2013年至今,是专业互联网技术服务公司,拥有项目网站设计制作、做网站网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元洞口做网站,已为上家服务,为洞口各地企业和个人服务,联系电话:028-86922220

没有具体的规则.

现在给你些资料

一.页面设计部分

1.img控件

alt:所有展示类图片都要具有能简要描述图片内容的文字说明。

2.Input控件

maxlength:所有INPUT控件都需要制定maxlength属性,默认值为数据库中对应的字段的长度。

readonly:所有不可更改的信息都要使用readonly属性。

3.Form控件

action:所有Form都要指定action,如果提交给本身就指定action=""

method:执行不可逆动作使用POST,可逆动作使用GET

onsubmit:所有form都要指定提交前需要的检查程序。

所有form都要有对应的reset button。

4.button控件

onclick:form中用于提交的button不容许使用此方法,所有数据检查通过form的onsubmit激活。

5.title属性

所有页面都要具有和本页标题相同的title。

6.控件的命名

采用控件类型缩写(小写)+英文单词(第一个字母大写)的方法。

开发中控件基本涉及一下几类

button:btn

form:frm

select:sel

textarea:txt

input:ipt

7.语言设置

所有中文页面都要加上如下语句:

meta http-equiv="Content-Language" content="zh-cn"

meta http-equiv="Content-Type" content="text/html; charset=gb2312"

8.控件属性赋值

所有控件的属性值都要使用双引号或者单引号包括起来。

二.客户端程序部分

1.错误提示信息的处理(2-1)

所有错误信息全部使用中文提示错误信息,标点使用中文半角符号,格式如下:

"错误:"+提示信息+"!"

2.成功提示信息的处理(2-2)

所有成功信息全部使用中文提示成功信息,标点使用中文半角符号,格式如下:

"成功:"+提示信息+"!"

3.页面的返回

所有需要返回上一页的时候使用history.back();不使用history.go(-1);

需要返回前n页(n1)时使用history.go(-n);

所有返回都使用连接的方式而不是button。

4.提交前数据的判断

保证提交前的数据都会通过JavaScript进行数据类型以及长度的判断

是否为数字:使用函数isNaN()

长度判断: 长度要判断去掉前后空格后的实际长度

为空判断: 所有不容许为空的输入字段都要在去掉前后空格后进行判断,同时如果该字段为查询条件则必须不能为空

如果判断条件发现数据错误,则通过(2-1)提示错误信息,然后通过方法focus()聚焦错误字段。

5.删除数据前的提示

所有涉及删除的操作,在用户选定以后都要再进行一次确认操作。

三.服务器端程序部分

1.数据的取得

通过Get,Post,连接传递过来的数据在使用前都要通过trim去掉数据前后的空格。

2.数据的判断

通过request的得到的参数数据需要再次进行空,类型,和长度的判断。

3.对象的关闭

所有数据库和文件对象都要在使用后尽可能早的close,同时赋nothing。

4.提示信息

所有错误提示信息使用JavaScript提示,保证使用者看不到任何内部错误信息。(如1-1)

涉及数据库Update,Del,Insert的操作成功都要提示。(如1-2)

5.变量的使用

所有变量在使用前都需要声明,并且赋初值。

6.变量的命名

采用变量类型缩写(小写)+英文单词(第一个字母大写)的方法。

开发中变量基本涉及一下几类

整数:i

小数:f

字符: s

布尔:b

日期:d

特殊的:

循环依次采用i,j,m,n;

数组用ary

指针p,q

临时变量tmp

七.SQL语句

1.排序

order时应该尽量提前使用建立索引或者主键的字段排序。

2.select

select时避免使用*,即使需要所有字段也应尽量一个一个按照使用的顺序罗列出来。

3.尽量避免使用in和not in

八.测试

所有页面要在800*600,1024*768两种分辨率下运行通过。

所有页面要在IE5.0,5.5以及6.0下运行通过没有JavaScript错误。

****************************************************************

WEB编码规范

编制人:walaqi

第一章 ASP编码规范通述

ASP编码分为两大部分,一部分为静态文件编码,一部分为包含服务器端脚本的动态文件编码。

静态文件编码分Script编码和HTML编码两部分。

服务器端编码则分为服务器脚本、客户端脚本、HTML脚本三部分。

编码规范采用如下约定:

所有客户端脚本一律使用JavaScript

所有服务器端脚本一律使用VBScript

静态页面输出一律使用HTML脚本

本规范不适用于由服务器端脚本所产生的客户端脚本代码。

第二章 静态文件编码规范:

静态文件脚本部分采用JavaScript编写。输出部分采用HTML标记语言。

1. HTML标记语言编码规范

1.1 标记的换行规范:

* 一个标记必须占用一行。不得出现两个标记在同一行的情况(同一标记的关闭标记除外),如:

trtdtext/td/tr

而必须写成:

tr

tdtext/td

tr

1.2 标记的关闭规范

* 静态文件内容必须包含在body/body标记中间

* body标记必须包含在html/html标记中间

* 对于需要关闭的标记,如:

htmltitlebodytabletrtdptextareaselectfontoptiondivspan

必须同其关闭标记同时出现。如

body…p…font…./font…./p…../body

* 不得出现交叉包含的语句,如:

pfont…../p/font

1.3 标记的属性赋值规范

对于接受属性的标记,属性值必须使用双引号或者单引号包围。如:

body bgcolor=”red”

font size=’7’

1.4 标记的缩进规范

* 最高一级的父标记采用左对齐顶格方式书写。

* 下一级标记采用左对齐向右缩进一个Tab的方式书写

在下一级依此类推,分别左对齐相对于父标记向右缩进一个Tab的方式书写

* 同一级标记的首字符上下必须对齐。

2. 客户端JavaScript规范

2.1 变量命名规范

* 常量以及全局变量名必须全部使用大写字母

* 变量名首字母必须小写。

* 变量名必须使用其类型的所写字符串开始。各种类型的所写字符串如下:

* 整型变量:int

* 长整型变量:lng

* 浮点型变量:flt

* 双精度变量:dbl

* 对象引用变量:obj

* 字符串变量:str

* Date类型变量:dtm

* 变量名必须采用有意义的单词命名,如:

strUserName、lngArrayIndex

* 变量名除首字母小写外,其他单词首字符必须大写

* 如果变量名过长可以使用单词缩写,除了被广泛了解的单词缩写以外,所有使用单词所写的变量名必须在定义时给出注释,如:

var strAdName //用于表示Administrator帐户的名称

var strAdminName //不用给出注释,Admin被广泛了解

2.2 变量使用规范

* 变量使用前必须定义。没有定义的变量禁止使用

* 变量的使用尽量缩小到小的作用域。如循环使用

for(var I=0;I12;I++){

}

而不是:

var I;

for(I=0;I12,I++){

}

2.3 对象命名规范

各种页面对象如text输入框、按钮、下拉选择框在命名时必须使用以下对应前缀:

* text输入框:txt

* button按钮:btn

* select下拉选择框:sel

* option项:opt

* form表单:frm

* frame框架:fra

* hidden表单项:hdn

* div标记:div

* span标记:span

* 对话框对象:dlg

* 窗口对象:win

2.4 函数以及子过程命名规范

* 函数命名必须使用动词+名词对的方式,并且能够体现函数的功能

* 函数命名的动词前缀必须是同函数功能相关的完整动词

* 函数命名第一个单词的首字母小写,后面每一个单词的首字母大写

第三章 动态文件编码规范

1. HTML书写规范

HTML书写规范必须符合静态文件HTML标记书写规范,参考(第二章第一节)

2. 客户端脚本规范

动态文件客户端脚本一律采用JavaScript书写,并必须符合静态文件编码规范中有关JavaScript编码规范的规定(参考第二章第二节)

3. 服务器端脚本书写规范

服务器端脚本书写采用VBScript书写

3.1 命名规范

3.1.1 VBScript脚本变量命名规范

* 常量以及全局变量必须全部使用大写字母

* 常量必须使用CONST_前缀

* 全局变量必须使用G_前缀

* 变量名首字母必须小写。

* 变量名必须使用其类型的所写字符串开始。各种类型的所写字符串如下:

* 整型变量:int

* 长整型变量:lng

* 浮点型变量:flt

* 双精度变量:dbl

* 对象引用变量:obj

* 字符串变量:str

* Date类型变量:dtm

* 变量名必须采用有意义的单词命名,如:

strUserName、lngArrayIndex

* 变量名除首字母小写外,其他单词首字符必须大写

* 如果变量名过长可以使用单词缩写,除了被广泛了解的单词缩写以外,所有使用单词所写的变量名必须在定义时给出注释,如:

dim strAdName ‘用于表示Administrator帐户的名称

dim strAdminName ‘不用给出注释,Admin被广泛了解

3.1.2 对象命名规范

各种对象如Connection、Recordset、Command在命名时必须使用以下对应前缀:

* Connection对象:conn

* Recordset对象:rs

* Command对象:cmd

* Parameter对象:param

* Field对象:fld

* Error对象:err

3.1.3 函数以及子过程命名规范

* 函数命名必须使用动词+名词对的方式,并且能够体现函数的功能

* 函数命名的动词前缀必须是同函数功能相关的完整动词

* 函数命名第一个单词的首字母大写,后面每一个单词的首字母大写

3.1.4 常用变量命名规范:

说明:包含在[]中的部分为可省略部分

* Connection对象:conn[Name]。Name为所连接数据库的服务器名字

* Recordset变量命名规范:rs[Name]。Name为自定义的同rs存储内容有关的英文单词组合

* Command对象:cmd[Name]。Name为自定义的同command目的有关的英文单词组合

* SQL语句字符串变量:strSql[CommandName]。CommandName为自定义的同Sql语句功能相关的英文单词组合,如:

strSqlUpdateModify

strSqlInsertUser

3.2 代码书写规范

3.2.1 变量明确声明原则

* 所有ASP程序文件,必须在代码的第一行包含%option explicit%。转为变量明确声明模式

3.2.2 字符集设定原则

* 所有将对客户端产生中文输出的ASP程序文件,必须在输出前设定Charset为”GB2312”.如:Response.Charset = “GB2312”

3.2.3 函数使用原则

* 尽量使用函数封装代码块

* 连续代码块尽量不要超过50行。最多不得超过70行

* 尽量使用局部变量。

* 如有涉及到全局的资源,如Connection,尽量作为函数的参数传入

* 所有在函数内部创建打开的资源,在退出函数前必须关闭释放。如:Recordset,Command

3.2.4 Request、Session、Application使用规范

* 所有需要放入Session、Application中的对象,必须采用有意义的英文名字。除了被广泛了解的单词缩写以外,不得采用单词缩写。如:

Session(“cp”) = strCurrentUserIP ‘不允许

Session(“CurrentUserIP”) = strCurrentUserIP

Session(“Pwd”) = strPwd ‘允许,Pwd被广泛了解为密码

* 所有需要在代码内用到的Request、Session、Application中的元素,必须在代码头部赋值给代码内声明的变量。

* 如果获得Form中提交的内容,必须使用Request.Form(“itemName”).

* 如果获得QueryString中提交的内容,必须使用Request.QueryString(“itemName”)

* 不得在代码中出现Request(“”)这样的引用方式

3.2.5 HTML同服务器端脚本混合使用原则

* 服务器端脚本标记“%”必须同其上一行紧邻的标记左对齐,如:

table

%

do while not rs.eof

%

tr

tdtext/td

/tr

%

rs.movenext

loop

%

/table

* 服务器端脚本标记“%”同其后的代码不得在同一行书写

* “%”同其前面的代码不得在同一行书写

* 服务器端脚本标记”%”同其最近的”%”标记对齐

* 服务器端内部的HTML代码依据静态文件的HTML缩进规则编写,不遵循服务器端脚本缩进规则

* HTML标记内部的代码,依据服务器端脚本的缩进规则,不遵循HTML代码缩进规则 。

第四章 常见错误

1. ADO的事务处理

1.1 错误代码:80004005。

1.1.1 错误描述:

Microsoft OLE DB Provider for ODBC Drivers 错误 ’80004005’

不能在 firehose 方式下启动事务

1.1.2 解决方法:

在开始ADO的事务的时候,必须首先关闭使用同一个连接对象打开的记录集,或者在打开那些游标集之前,设置游标集位置类型为adUseClient.(使用客户端游标集)

第五章 代码习惯书写示例

1. ADO对象的使用

1.1 ADODB.Connection对象

1.2 ADODB.Command对象

1.3 ADODB.Recordset对象

1.3.1 创建:

Set rs = Server.CreateObject(“ADODB.Recordset”)

rs.CursorLocation = adUseClient

rs.Open strSql,conn,1[,1] ‘必须指定游标类型

一、 注释规范

A. 注释标准:

l 功能注释

功能注释是指为了对代码本身进行解释说明而进行的注释。

注释符采用“’”作为统一的注释符。

1.行内注释

采用注释符号 “’”

例:

Dim intFileNo As Integer ’ファイル番号取得用

2.整行(包括多行)注释

采用注释块开始与块结束标志

36

’************************************

’************************************

l 修订注释

修订注释是指出于测试或者改错等目的,对代码进行了更改,而必须对此修改提供相关说明和醒目标记,并将原来的代码加入注释块内。

只要有改动,无论单行还是多行均采用设置注释块开始与块结束标志的方法来明确标志修改部分,清楚地进行解释说明,便于查找和分辨注释比较多的代码段。

15 15

’*************** Modify Start ***************

’*************** End ***************

B. 需要注释的地方:

声明定义部分

对每个常量声明进行注释;

对每个变量及类、对象等的声明进行注释;

对每个自定义函数定义进行注释;

对每个自定义子程序定义进行注释;

代码部分

对每个构件,在顶部进行注释;

对每个条件选择分支进行注释;

对每个详细设计中提到的关键点进行注释;

对全局变量的使用进行注释;

C. 注释的内容:

l 对变量及常量声明部分的注释以行内注释方式简要描述其用途。

l 自定义函数及子程序等定义部分的顶部进行注释:

’************************************

’ 概要:

’ 机能说明:

’ 参数说明:

’ 返回值:

’ 备注:

’************************************

l 代码内部的行内注释

说明具体代码的运算规则,循环的内容,计数器的目的等等。

l 修订注释

’*************** Modify Start ***************

’ 修订原因:

’ 修订履历:

’ 修订者 修订日期

’ 原始代码:

’ Case 5 To 8

’ ……

Case 4 To 8

’*************** End ***************

D. 注释的方法:

对代码行可以在行尾加注释(不能违反行宽的要求);

对单行代码的注释可以在上一行以“’”的形式添加简单注释;

对整段代码的注释放在代码段之前;

注释符统一采用“’”。

二、命名规范

A.通则

VisualBasic保留字可在VisualBasic设计器中根据颜色的变化看到。变量命名不可以使用保留字,应使用有意义的名字命名,不可使用简称和无意义的名称诸如A,x1等。即便对于只用于循环计数的变量,也应该统一赋予有意义的名称,例如longCnt等。

不能起太长的名字,应该尽量简洁,长度限制应控制在32个字符之内。

B.常数

全部使用大写字母以表明常数意义的名词命名,不区分常数的类型:

Const DEFAULTCONCENTRATION As Single = 0.01

C.变量

命名必须使用大小写结合(VB编辑器会自动转换以减少程序出错的机率)

变量命名采用[范围前缀][数组前缀][类型前缀]+[自定义命名]

控件命名采用[控件前缀]+[自定义命名]

变量范围做前缀

范围 前缀 例子

全局变量 g gStrUserName

模块级 m mStrUserName

过程级 无 StrUserName

数组前缀: a

类型前缀:

数据类型 前缀 例子

Boolean Bln BlnFound

Byte Byt BytRasterDate

Currency Cur CurBalance

Date Dtm DtmBeginDate

Double Dbl DblFee

Integer Int IntQty

Long Lng LngVcID

Single Sng SngAverage

String Str StrItemId

Object Obj ObjRmtsvr

ADODB.Recordset Rst RstItem

ADODB.Connection Cnn cnnNewsPaper

ADODB.Command Cmm CmmAddCustomer

Variant Vnt VntCheck

自定义类型 Udt UdtUserInfo

控件类型命名前缀

控件类型 前缀 例子

ADO Data ado AdoBiblio

Check box chk ChkReadOnly

Combo box, drop-down list box cbo CboEnglish

Command button cmd CmdExit

Common dialog dlg DlgFileOpen

Data-bound combo box dbcbo DbcboLanguage

Data-bound grid dbgrd DbgrdQueryResult

Data-bound list box dblst DblstJobType

Data combo dbc DbcAuthor

Data grid dgd DgdTitles

Data list dbl DblPublisher

Directory list box dir DirSource

Drive list box drv DrvTarget

File list box fil FilSource

Form frm FrmEntry

Frame fra FraLanguage

Graph gra GraRevenue

Grid grd GrdPrices

Horizontal scroll bar hsb HsbVolume

Image img ImgIcon

Image combo imgcbo ImgcboProduct

ImageList ils IlsAllIcons

Label lbl LblHelpMessage

Line lin LinVertical

List box lst LstPolicyCodes

ListView lvw LvwHeadings

Menu mnu MnuFileOpen

Month view mvw MvwPeriod

MS Chart ch ChSalesbyRegion

MS Flex grid msg MsgClients

MS Tab mst MstFirst

Option button opt OptGender

Picture box pic PicVGA

ProgressBar prg PrgLoadFile

Remote Data rd RdTitles

Slider sld SldScale

Spin spn SpnPages

StatusBar sta StaDateTime

SysInfo sys SysMonitor

TabStrip tab TabOptions

Text box txt TxtLastName

Timer tmr TmrAlarm

Toolbar tlb TlbActions

TreeView tre TreOrganization

UpDown upd UpdDirection

Vertical scroll bar vsb VsbRate

自行开发ActiveX控件的前缀根据具体项目的设计时规定。

D. 标签

标签就是用于Goto跳转的代码标识,由于Goto并不推荐使用,所以标签的使用也比较苛刻。标签必须全部大写,中间的空格用下划线_代替,而且应该以_开头,比如:

_A_LABEL_EXAMPLE:

如此定义标签是为了与其他代码元素充分区别。

E.方法

无论是函数还是子程序,方法都必须以动词或动词短语命名。无需区分函数和子程序,也无需指明返回类型。

Sub Open(ByVal StrCommandString As String)

Function SetCopyNumber(ByVal IntCopyNumber As Integer) as Integer

参数需要指明ByVal还是ByRef,这一点写起来会让程序变长,但非常必要。如果没有特别情况,都使用ByVal。参数的命名方法,参考 “变量的命名方法”。

三、 书写格式规范

A. 程序的书写顺序

该构件的概要注释说明

变量声明

过程声明

代码段1

代码段2

……

B. 大小写

变量名范围前缀用小写,每个单词第一个字母用大写

函数、过程、对象名也要求每个组成单词字首大写

C. 缩进

统一开发环境,设定VisualBasic设计器的开发环境选项,定义Tab宽度为4。代码缩进时,先选中要缩进的代码块,然后使用快捷键是Tab(右移)和Shift+Tab(左移);如果手工输入空格完成缩进,以4个空格为单位。

在If语句后缩进;

在Else语句后缩进

在Select Case语句后缩进

在Case语句后缩进

在Do语句后缩进

在For语句后缩进

已经用行接续符分割的语句的各个行要缩进

在With语句后缩进。

对从属于行标注的代码进行缩进。

D. 空格

运算符前后都要空格,包括:+,-,*,/,^,=,,=,,=,,NOT,AND,OR等;

E. 空行

变量声明部分和代码语句间的分隔;

在执行统一任务的各个语句组之间插入一个空行。好的代码应由按逻辑顺序排列的进程或相关语句组构成。

F. 页宽

对较长语句,如API声明等,在代码窗体可视范围内给予换行,不要使别人必须通过滚动窗口才能查看到完整的代码,单行代码长度不超过95列。

使用“ _ ”换行符。

G. 其他

在项目组内部,根据需要统一VisualBasic开发环境参数。

四、 代码检查

代码检查的合格标准

注释完整、命名规范、条理清晰、可读性强的代码视为合格代码。

检查办法

发现未遵循本编码规范的情况视为不合格;

五、 建议性规范

l 有的时候可能需要违背好的编程原则,或者使用了某些不正规的方法,遇到这种情况时,必须用详细的注释来说明在做什么和为什么要这样做。

技巧性特别高的代码段,一定要加详细的注释,不要让其他开发人员花很长时间来研究一个高技巧但不易理解的程序段。

l 对注释进行缩进,使之与后随的语句对齐。

注释通常位于它们要说明的代码的前面。为了从视觉上突出注释与它的代码之间的关系,请将注释缩进,使之与代码处于同一个层次上

六、 其他

对文档的理解产生的歧义由引用此文档的项目的项目负责人统一解释。

相信对你有所帮助

驳狗屎文 "我为什么放弃Go语言

此篇文章流传甚广, 其实里面没啥干货, 而且里面很多观点是有问题的. 这个文章在 golang-china 很早就讨论过了.

最近因为 Rust 1.0 和 1.1 的发布, 导致这个文章又出来毒害读者.

所以写了这篇反驳文章, 指出其中的问题.

有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题。

开门见山地说,我当初放弃Go语言(golang),就是因为两个“不爽”:第一,对Go语言本身不爽;第二,对Go语言社区里的某些人不爽。毫无疑问,这是非常主观的结论。但是我有足够详实的客观的论据,用以支撑这个看似主观的结论。

文末附有本文更新日志。

确实是非常主观的结论, 因为里面有不少有问题的观点(用来忽悠Go小白还行).

第0节:我的Go语言经历

先说说我的经历吧,以避免被无缘无故地当作Go语言的低级黑。

2009年底,Go语言(golang)第一个公开版本发布,笼罩着“Google公司制造”的光环,吸引了许多慕名而来的尝鲜者,我(Liigo)也身居其中,笼统的看了一些Go语言的资料,学习了基础的教程,因对其语法中的分号和花括号不满,很快就遗忘掉了,没拿它当一回事。

在2009年Go刚发布时, 确实是因为“Google公司制造”的光环而吸引了(包括文章作者和诸多IT记者)很多低级的尝鲜者.

还好, 经过5年的发展, 这些纯粹因为光环来的投机者所剩已经不多了(Google趋势).

目前, 真正的Go用户早就将Go用于实际的生产了.

说到 其语法中的分号和花括号不满, 我想说这只是你的 个人主观感受, 还有很多人对Go的分号和花括号很满意,

包括水果公司的的 Swift 的语言设计者也很满意这种风格(Swift中的分号和花括号和Go基本相同).

如果只谈 个人主观感受, 我也可以说 Rust 的 fn 缩写也很蛋疼!

两年之后,2011年底,Go语言发布1.0的计划被提上日程,相关的报道又多起来,我再次关注它,重新评估之后决定深入参与Go语言。我订阅了其users、nuts、dev、commits等官方邮件组,坚持每天阅读其中的电子邮件,以及开发者提交的每一次源代码更新,给Go提交了许多改进意见,甚至包括修改Go语言编译器源代码直接参与开发任务。如此持续了数月时间。

这个到是事实, 在 golang-china 有不少吵架的帖子, 感兴趣的可以去挖下, 我就不展开说了.

到2012年初,Go 1.0发布,语言和标准库都已经基本定型,不可能再有大幅改进,我对Go语言未能在1.0定型之前更上一个台阶、实现自我突破,甚至带着诸多明显缺陷走向1.0,感到非常失望,因而逐渐疏远了它(所以Go 1.0之后的事情我很少关心)。后来看到即将发布的Go 1.1的Release Note,发现语言层面没有太大改变,只是在库和工具层面有所修补和改进,感到它尚在幼年就失去成长的动力,越发失望。外加Go语言社区里的某些人,其中也包括Google公司负责开发Go语言的某些人,其态度、言行,让我极度厌恶,促使我决绝地离弃Go语言。

真的不清楚楼主说的可以在 Go1.0 之前短时间内能实现的 重大改进和诸多明显缺陷 是什么.

如果是楼主说前面的 其语法中的分号和花括号不满 之类的重大改进, 我只能说这只是你的 个人主观感受 而已,

你的很多想法只能说服你自己, 没办法说服其他绝大部分人(不要以为像C++或Rust那样什么特性都有就NB了, 各种NB特性加到一起只能是 要你命3000, 而绝对不会是什么 银弹).

Go 1.1的Release Note,发现语言层面没有太大改变. 语言层没有改变是是因为 Go1 作出的向后兼容的承诺. 对于工业级的语言来说, Go1 这个只能是优点. 如果连语言层在每个版本都会出现诸多大幅改进, 那谁还敢用Go语言来做生产开发呢(我承认Rust的改动很大胆, 但也说明了Rust还处于比较幼稚和任性的阶段)?

说 Go语言社区里的某些人固执 的观点我是同意的. 但是这些 固执 的人是可以讲道理的, 但是他们对很多东西的要求很高(特别是关于Go的设计哲学部分).

只要你给的建议有依据(语言的设计哲学是另外一回事情), 他们绝对不会盲目的拒绝(只是讨论的周期会比较长).

关于楼主提交的给Go文件添加BOM的文章, 需要补充说明下.

在Go1.0发布的时候, Go语言的源文件(.go)明确要求必须是UTF8编码的, 而且是无BOM的UTF8编码的.

注意: 这个 无BOM的UTF8编码 的限制仅仅是 针对 Go语言的源文件(.go).

这个限制并不是说不允许用户处理带BOM的UTF8的txt文件!

我觉得对于写Go程序来说, 这个限制是没有任何问题的, 到目前为止, 我还从来没有使用过带BOM的.go文件.

不仅是因为带BOM的.go文件没有太多的意义, 而且有很多的缺陷.

BOM的原意是用来表示编码是大端还是小端的, 主要用于UTF16和UTF32. 对于 UTF8 来说, BOM 没有任何存在的意义(正是Go的2个作者发明了UTF8, 彻底解决了全球的编码问题).

但是, 在现实中, 因为MS的txt记事本, 对于中文环境会将txt(甚至是C/C++源文件)当作GBK编码(GBK是个烂编码),

为了区别到底是GBK还是UTF8, MS的记事本在前面加了BOM这个垃圾(被GBK占了茅坑), 这里的bom已经不是表示字节序本意了. 不知道有没有人用ms的记事本写网页, 然后生成一个带bom的utf8网页肯定很有意思.

这是MS的记事本的BUG: 它不支持生成无BOM的UTF8编码的文本文件!

这些是现实存在的带BOM的UTF8编码的文本文件, 但是它们肯定都不是Go语言源文件!

所以说, Go语言的源文件即使强制限制了无BOM的UTF8编码要求, 也是没有任何问题的(而且我还希望有这个限制).

虽然后来Go源文件接受带BOM的UTF8了, 但是运行 go fmt 之后, 还是会删除掉BOM的(因为BOM就是然并卵). 也就是说 带 BOM 的 Go 源文件是不符合 Go语言的编码风格的, go fmt 会强制删除 BOM 头.

前面说了BOM是MS带来的垃圾, 但是BOM的UTF8除了然并卵之外还有很多问题, 因为BOM在string的开头嵌入了垃圾,

导致正则表达式, string的链接运算等操作都被会被BOM这个垃圾所污染. 对于.go语言, 即使代码完全一样, 有BOM和无BOM会导致文件的MD5之类的校验码不同.

所以, 我觉得Go用户不用纠结BOM这个无关紧要的东西.

在上一个10年,我(Liigo)在我所属的公司里,深度参与了两个编程语言项目的开发。我想,对于如何判断某个编程语言的优劣,或者说至少对于如何判断某个编程语言是否适合于我自己,我应该还是有一点发言权的。

第1节:我为什么对Go语言不爽?

Go语言有很多让我不爽之处,这里列出我现在还能记起的其中一部分,排名基本上不分先后。读者们耐心地看完之后,还能淡定地说一句“我不在乎”吗?

1.1 不允许左花括号另起一行

关于对花括号的摆放,在C语言、C++、Java、C#等社区中,十余年来存在持续争议,从未形成一致意见。在我看来,这本来就是主观倾向很重的抉择,不违反原则不涉及是非的情况下,不应该搞一刀切,让程序员或团队自己选择就足够了。编程语言本身强行限制,把自己的喜好强加给别人,得不偿失。无论倾向于其中任意一种,必然得罪与其对立的一群人。虽然我现在已经习惯了把左花括号放在行尾,但一想到被禁止其他选择,就感到十分不爽。Go语言这这个问题上,没有做到“团结一切可以团结的力量”不说,还有意给自己树敌,太失败了。

我觉得Go最伟大的发明是 go fmt, 从此Go用户不会再有花括弧的位置这种无聊争论了(当然也少了不少灌水和上tiobe排名的机会).

是这优点, Swift 语言也使用和 Go 类似的风格(当然楼主也可能鄙视swift的作者).

1.2 编译器莫名其妙地给行尾加上分号

对Go语言本身而言,行尾的分号是可以省略的。但是在其编译器(gc)的实现中,为了方便编译器开发者,却在词法分析阶段强行添加了行尾的分号,反过来又影响到语言规范,对“怎样添加分号”做出特殊规定。这种变态做法前无古人。在左花括号被意外放到下一行行首的情况下,它自动在上一行行尾添加的分号,会导致莫名其妙的编译错误(Go 1.0之前),连它自己都解释不明白。如果实在处理不好分号,干脆不要省略分号得了;或者,Scala和JavaScript的编译器是开源的,跟它们学学怎么处理省略行尾分号可以吗?

又是楼主的 个人主观感受, 不过我很喜欢这个特性. Swift 语言也是类似.

1.3 极度强调编译速度,不惜放弃本应提供的功能

程序员是人不是神,编码过程中免不了因为大意或疏忽犯一些错。其中有一些,是大家集体性的很容易就中招的错误(Go语言里的例子我暂时想不起来,C++里的例子有“基类析构函数不是虚函数”)。这时候编译器应该站出来,多做一些检查、约束、核对性工作,尽量阻止常规错误的发生,尽量不让有潜在错误的代码编译通过,必要时给出一些警告或提示,让程序员留意。编译器不就是机器么,不就是应该多做脏活累活杂活、减少人的心智负担么?编译器多做一项检查,可能会避免数十万程序员今后多年内无数次犯同样的错误,节省的时间不计其数,这是功德无量的好事。但是Go编译器的作者们可不这么想,他们不愿意自己多花几个小时给编译器增加新功能,觉得那是亏本,反而减慢了编译速度。他们以影响编译速度为由,拒绝了很多对编译器改进的要求。典型的因噎废食。强调编译速度固然值得赞赏,但如果因此放弃应有的功能,我不赞成。

编译速度是很重要的, 如果编译速度够慢, 语言再好也不会有人使用的.

比如C/C++的增量编译/预编译头文件/并发编译都是为了提高编译速度.

Rust1.1 也号称 比 1.0 的编译时间减少了32% (注意: 不是运行速度).

当然, Go刚面世的时候, 编译速度是其中的一个设计目标.

不过我想楼主, 可能想说的是因为编译器自己添加分号而导致的编译错误的问题.

我觉得Go中 { 不能另起一行是语言特性, 如果修复这个就是引入了新的错误.

其他的我真想不起来还有哪些 调编译速度,不惜放弃本应提供的功能 (不要提泛型, 那是因为还没有好的设计).

1.4 错误处理机制太原始

在Go语言中处理错误的基本模式是:函数通常返回多个值,其中最后一个值是error类型,用于表示错误类型极其描述;调用者每次调用完一个函数,都需要检查这个error并进行相应的错误处理:if err != nil { /*这种代码写多了不想吐么*/ }。此模式跟C语言那种很原始的错误处理相比如出一辙,并无实质性改进。实际应用中很容易形成多层嵌套的if else语句,可以想一想这个编码场景:先判断文件是否存在,如果存在则打开文件,如果打开成功则读取文件,如果读取成功再写入一段数据,最后关闭文件,别忘了还要处理每一步骤中出现错误的情况,这代码写出来得有多变态、多丑陋?实践中普遍的做法是,判断操作出错后提前return,以避免多层花括号嵌套,但这么做的后果是,许多错误处理代码被放在前面突出的位置,常规的处理逻辑反而被掩埋到后面去了,代码可读性极差。而且,error对象的标准接口只能返回一个错误文本,有时候调用者为了区分不同的错误类型,甚至需要解析该文本。除此之外,你只能手工强制转换error类型到特定子类型(静态类型的优势没了)。至于panic - recover机制,致命的缺陷是不能跨越库的边界使用,注定是一个半成品,最多只能在自己的pkg里面玩一玩。Java的异常处理虽然也有自身的问题(比如Checked Exceptions),但总体上还是比Go的错误处理高明很多。

话说, 软件开发都发展了半个世纪, 还是无实质性改进. 不要以为弄一个异常的语法糖就是革命了.

我只能说错误和异常是2个不同的东西, 将所有错误当作异常那是SB行为.

正因为有异常这个所谓的银弹, 导致很多等着别人帮忙擦屁股的行为(注意 shit 函数抛出的绝对不会是一种类型的 shit, 而被其间接调用的各种 xxx_shit 也可能抛出各种类型的异常, 这就导致 catch 失控了):

int main() {

try {

shit();

} catch( /* 到底有几千种 shit ? */) {

...

}

}

Go的建议是 panic - recover 不跨越边界, 也就是要求正常的错误要由pkg的处理掉.

这是负责任的行为.

再说Go是面向并发的编程语言, 在海量的 goroutine 中使用 try/catch 是不是有一种不伦不类的感觉呢?

1.5 垃圾回收器(GC)不完善、有重大缺陷

在Go 1.0前夕,其垃圾回收器在32位环境下有内存泄漏,一直拖着不肯改进,这且不说。Go语言垃圾回收器真正致命的缺陷是,会导致整个进程不可预知的间歇性停顿。像某些大型后台服务程序,如游戏服务器、APP容器等,由于占用内存巨大,其内存对象数量极多,GC完成一次回收周期,可能需要数秒甚至更长时间,这段时间内,整个服务进程是阻塞的、停顿的,在外界看来就是服务中断、无响应,再牛逼的并发机制到了这里统统失效。垃圾回收器定期启动,每次启动就导致短暂的服务中断,这样下去,还有人敢用吗?这可是后台服务器进程,是Go语言的重点应用领域。以上现象可不是我假设出来的,而是事实存在的现实问题,受其严重困扰的也不是一家两家了(2013年底ECUG Con 2013,京东的刘奇提到了Go语言的GC、defer、标准库实现是性能杀手,最大的痛苦是GC;美团的沈锋也提到Go语言的GC导致后台服务间隔性停顿是最大的问题。更早的网络游戏仙侠道开发团队也曾受Go垃圾回收的沉重打击)。在实践中,你必须努力减少进程中的对象数量,以便把GC导致的间歇性停顿控制在可接受范围内。除此之外你别无选择(难道你还想自己更换GC算法、甚至砍掉GC?那还是Go语言吗?)。跳出圈外,我近期一直在思考,一定需要垃圾回收器吗?没有垃圾回收器就一定是历史的倒退吗?(可能会新写一篇博客文章专题探讨。)

这是说的是32位系统, 这绝对不是Go语言的重点应用领域!! 我可以说Go出生就是面向64位系统和多核心CPU环境设计的. (再说 Rust 目前好像还不支持 XP 吧, 这可不可以算是影响巨大?)

32位当时是有问题, 但是对实际生产影响并不大(请问楼主还是在用32位系统吗, 还只安装4GB的内存吗). 如果是8位单片机环境, 建议就不要用Go语言了, 直接C语言好了.

而且这个问题早就不存在了(大家可以去看Go的发布日志).

Go的出生也就5年时间, GC的完善和改进是一个持续的工作, 2015年8月将发布的 Go1.5将采用并行GC.

关于GC的被人诟病的地方是会导致卡顿, 但是我以为这个主要是因为GC的实现还不够完美而导致的.

如果是完美的并发和增量的GC, 那应该不会出现大的卡顿问题的.

当然, 如果非要实时性, 那用C好了(实时并不表示性能高, 只是响应时间可控).

对于Rust之类没有GC的语言来说, 想很方便的开发并发的后台程序那几乎是不可能的.

不要总是吹Rust能代替底层/中层/上层的开发, 我们要看有谁用Rust真的做了什么.

1.6 禁止未使用变量和多余import

Go编译器不允许存在被未被使用的变量和多余的import,如果存在,必然导致编译错误。但是现实情况是,在代码编写、重构、调试过程中,例如,临时性的注释掉一行代码,很容易就会导致同时出现未使用的变量和多余的import,直接编译错误了,你必须相应的把变量定义注释掉,再翻页回到文件首部把多余的import也注释掉,……等事情办完了,想把刚才注释的代码找回来,又要好几个麻烦的步骤。还有一个让人蛋疼的问题,编写数据库相关的代码时,如果你import某数据库驱动的pkg,它编译给你报错,说不需要import这个未被使用的pkg;但如果你听信编译器的话删掉该import,编译是通过了,运行时必然报错,说找不到数据库驱动;你看看程序员被折腾的两边不是人,最后不得不请出大神:import _。对待这种问题,一个比较好的解决方案是,视其为编译警告而非编译错误。但是Go语言开发者很固执,不容许这种折中方案。

这个问题我只能说楼主的吐槽真的是没水平.

为何不使用的是错误而不是警告? 这是为了将低级的bug消灭在编译阶段(大家可以想下C/C++的那么多警告有什么卵用).

而且, import 即使没有使用的话, 也是用副作用的, 因为 import 会导致 init 和全局变量的初始化.

如果某些代码没有使用, 为何要执行 init 这些初始化呢?

如果是因为调试而添加的变量, 那么调试完删除不是很正常的要求吗?

如果是因为调试而要导入fmt或log之类的包, 删除调试代码后又导致 import 错误的花,

楼主难道不知道在一个独立的文件包装下类似的辅助调试的函数吗?

import (

"fmt"

"log"

)

func logf(format string, a ...interface{}) {

file, line := callerFileLine()

fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)

fmt.Fprintf(os.Stderr, format, a...)

}

func fatalf(format string, a ...interface{}) {

file, line := callerFileLine()

fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)

fmt.Fprintf(os.Stderr, format, a...)

os.Exit(1)

}

import _ 是有明确行为的用法, 就是为了执行包中的 init 等函数(可以做某些注册操作).

将警告当作错误是Go的一个哲学, 当然在楼主看来这是白痴做法.

1.7 创建对象的方式太多令人纠结

创建对象的方式,调用new函数、调用make函数、调用New方法、使用花括号语法直接初始化结构体,你选哪一种?不好选择,因为没有一个固定的模式。从实践中看,如果要创建一个语言内置类型(如channel、map)的对象,通常用make函数创建;如果要创建标准库或第三方库定义的类型的对象,首先要去文档里找一下有没有New方法,如果有就最好调用New方法创建对象,如果没有New方法,则退而求其次,用初始化结构体的方式创建其对象。这个过程颇为周折,不像C++、Java、C#那样直接new就行了。

C++的new是狗屎. new导致的问题是构造函数和普通函数的行为不一致, 这个补丁特性真的没啥优越的.

我还是喜欢C语言的 fopen 和 malloc 之类构造函数, 构造函数就是普通函数, Go语言中也是这样.

C++中, 除了构造不兼容普通函数, 析构函数也是不兼容普通函数. 这个而引入的坑有很多吧.

1.8 对象没有构造函数和析构函数

没有构造函数还好说,毕竟还有自定义的New方法,大致也算是构造函数了。没有析构函数就比较难受了,没法实现RAII。额外的人工处理资源清理工作,无疑加重了程序员的心智负担。没人性啊,还嫌我们程序员加班还少吗?C++里有析构函数,Java里虽然没有析构函数但是有人家finally语句啊,Go呢,什么都没有。没错,你有个defer,可是那个defer问题更大,详见下文吧。

defer 可以覆盖析构函数的行为, 当然 defer 还有其他的任务. Swift2.0 也引入了一个简化版的 defer 特性.

1.9 defer语句的语义设定不甚合理

Go语言设计defer语句的出发点是好的,把释放资源的“代码”放在靠近创建资源的地方,但把释放资源的“动作”推迟(defer)到函数返回前执行。遗憾的是其执行时机的设置似乎有些不甚合理。设想有一个需要长期运行的函数,其中有无限循环语句,在循环体内不断的创建资源(或分配内存),并用defer语句确保释放。由于函数一直运行没有返回,所有defer语句都得不到执行,循环过程中创建的大量短暂性资源一直积累着,得不到回收。而且,系统为了存储defer列表还要额外占用资源,也是持续增加的。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃。像这类长期运行的函数,http.ListenAndServe()就是典型的例子。在Go语言重点应用领域,可以说几乎每一个后台服务程序都必然有这么一类函数,往往还都是程序的核心部分。如果程序员不小心在这些函数中使用了defer语句,可以说后患无穷。如果语言设计者把defer的语义设定为在所属代码块结束时(而非函数返回时)执行,是不是更好一点呢?可是Go 1.0早已发布定型,为了保持向后兼容性,已经不可能改变了。小心使用defer语句!一不小心就中招。

前面说到 defer 还有其他的任务, 也就是 defer 中执行的 recover 可以捕获 panic 抛出的异常.

还有 defer 可以在 return 之后修改命名的返回值.

上面2个工作要求 defer 只能在函数退出时来执行.

楼主说的 defer 是类似 Swift2.0 中 defer 的行为, 但是 Swift2.0 中 defer 是没有前面2个特性的.

Go中的defer是以函数作用域作为触发的条件的, 是会导致楼主说的在 for 中执行的错误用法(哪个语言没有坑呢?).

不过 for 中 局部 defer 也是有办法的 (Go中的defer是以函数作用域):

for {

func(){

f, err := os.Open(...)

defer f.Close()

}()

}

在 for 中做一个闭包函数就可以了. 自己不会用不要怪别人没告诉你.

1.10 许多语言内置设施不支持用户定义的类型

for in、make、range、channel、map等都仅支持语言内置类型,不支持用户定义的类型(?)。用户定义的类型没法支持for in循环,用户不能编写像make、range那样“参数类型和个数”甚至“返回值类型和个数”都可变的函数,不能编写像channel、map那样类似泛型的数据类型。语言内置的那些东西,处处充斥着斧凿的痕迹。这体现了语言设计的局限性、封闭性、不完善,可扩展性差,像是新手作品——且不论其设计者和实现者如何权威。延伸阅读:Go语言是30年前的陈旧设计思想,用户定义的东西几乎都是二等公民(Tikhon Jelvis)。

说到底, 这个是因为对泛型支持的不完备导致的.

Go语言是没啥NB的特性, 但是Go的特性和工具组合在一起就是好用.

这就是Go语言NB的地方.

1.11 没有泛型支持,常见数据类型接口丑陋

没有泛型的话,List、Set、Tree这些常见的基础性数据类型的接口就只能很丑陋:放进去的对象是一个具体的类型,取出来之后成了无类型的interface{}(可以视为所有类型的基础类型),还得强制类型转换之后才能继续使用,令人无语。Go语言缺少min、max这类函数,求数值绝对值的函数abs只接收/返回双精度小数类型,排序接口只能借助sort.Interface无奈的回避了被比较对象的类型,等等等等,都是没有泛型导致的结果。没有泛型,接口很难优雅起来。Go开发者没有明确拒绝泛型,只是说还没有找到很好的方法实现泛型(能不能学学已经开源的语言呀)。现实是,Go 1.0已经定型,泛型还没有,那些丑陋的接口为了保持向后兼容必须长期存在着。

Go有自己的哲学, 如果能有和目前哲学不冲突的泛型实现, 他们是不会反对的.

如果只是简单学学(或者叫抄袭)已经开源的语言的语法, 那是C++的设计风格(或者说C++从来都是这样设计的, 有什么特性就抄什么), 导致了各种脑裂的编程风格.

编译时泛型和运行时泛型可能是无法完全兼容的, 看这个例子:

type AdderT interface {

Add(a, b T) T

}

director,ligo语言里面的status怎么用,老是报错,put sound(1).status怎么在If语句里使用

没想到这年代还有人在学习Director,已经比较少有人知道Lingo了。全局变量放在帧面板最下部有个专门放代码的地方,左侧图标能看出来,这一点与flash相比是不一样的。

判断状态可以用循环,或者帧跳转的循环检测都可以。但是精灵的运用原理也与flash不同,不方便写那种帧里帧外的代码。所以一般代码都是写在时间轴帧面板底下的代码帧,那附近还有声音帧的。

播放器没写过,以前都是用director开发多媒体教学软件和教学游戏的。不过其实director功能特别强大值得好好研究,尤其后来3D的部分。不过director的·shockwave播放器普及度不高。

希望能帮到你 :)

go语言中全局变量和局部变量的区别

局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

以下实例中 main() 函数使用了局部变量 a, b, c:

package main

import "fmt"

func main() {

/* 声明局部变量 */

var a, b, c int

/* 初始化参数 */

a = 10

b = 20

c = a + b

fmt.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)

}

以上实例执行输出结果为:

结果: a = 10, b = 20 and c = 30

全局变量

在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

全局变量可以在任何函数中使用,以下实例演示了如何使用全局变量:

package main

import "fmt"

/* 声明全局变量 */

var g int

func main() {

/* 声明局部变量 */

var a, b int

/* 初始化参数 */

a = 10

b = 20

g = a + b

fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)

}

以上实例执行输出结果为:

结果: a = 10, b = 20 and g = 30

Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。实例如下:

package main

import "fmt"

/* 声明全局变量 */

var g int = 20

func main() {

/* 声明局部变量 */

var g int = 10

fmt.Printf ("结果: g = %d\n", g)

}

以上实例执行输出结果为:

结果: g = 10

Go语言变量的作用域

2021-10-22

每一个变量(常量、类型或函数)在程序中都有一定的作用范围。称之为作用域。

Go语言在编译时会检查每一个变量是否使用过,未使用过的变量就会编译错误。

根据变量定义位置的不同,可以分为以下三个类型:

在函数体内被声明的变量称之为局部变量,作用在函数体内,函数的参数和返回值变量都属于局部变量。局部变量不会一直存在,在函数被调用时存在,函数调用结束后变量就会被销毁,即生命周期。

例子:其中a、b均为局部变量,只会在main函数内有效

在函数体外被声明的变量称之为全局变量,作用于所有源文件。不包含这个全局变量的源文件需要使用"import"关键字引入全局变量所在的源文件之后才能使用这个全局变量。

全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。

例如:global为全局在main2和main函数中都能使用

函数名后面的小括号里定义的变量, 用于接受来自调用函数的参数。用于接收调用该函数时传入的参数。

例如:下面的例子中,第十七行a、b为sum函数定义的形参,用于传入main函数中的AF、BF

【golang详解】go语言GMP(GPM)原理和调度

Goroutine调度是一个很复杂的机制,下面尝试用简单的语言描述一下Goroutine调度机制,想要对其有更深入的了解可以去研读一下源码。

首先介绍一下GMP什么意思:

G ----------- goroutine: 即Go协程,每个go关键字都会创建一个协程。

M ---------- thread内核级线程,所有的G都要放在M上才能运行。

P ----------- processor处理器,调度G到M上,其维护了一个队列,存储了所有需要它来调度的G。

Goroutine 调度器P和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行

模型图:

避免频繁的创建、销毁线程,而是对线程的复用。

1)work stealing机制

当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程。

2)hand off机制

当本线程M0因为G0进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行。进而某个空闲的M1获取P,继续执行P队列中剩下的G。而M0由于陷入系统调用而进被阻塞,M1接替M0的工作,只要P不空闲,就可以保证充分利用CPU。M1的来源有可能是M的缓存池,也可能是新建的。当G0系统调用结束后,根据M0是否能获取到P,将会将G0做不同的处理:

如果有空闲的P,则获取一个P,继续执行G0。

如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度。然后M0将进入缓存池睡眠。

如下图

GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行

在Go中一个goroutine最多占用CPU 10ms,防止其他goroutine被饿死。

具体可以去看另一篇文章

【Golang详解】go语言调度机制 抢占式调度

当创建一个新的G之后优先加入本地队列,如果本地队列满了,会将本地队列的G移动到全局队列里面,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G。

协程经历过程

我们创建一个协程 go func()经历过程如下图:

说明:

这里有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中;处理器本地队列是一个使用数组构成的环形链表,它最多可以存储 256 个待执行任务。

G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;

一个M调度G执行的过程是一个循环机制;会一直从本地队列或全局队列中获取G

上面说到P的个数默认等于CPU核数,每个M必须持有一个P才可以执行G,一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用。类似线程池,Go也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个。

work-stealing调度算法:当M执行完了当前P的本地队列队列里的所有G后,P也不会就这么在那躺尸啥都不干,它会先尝试从全局队列队列寻找G来执行,如果全局队列为空,它会随机挑选另外一个P,从它的队列里中拿走一半的G到自己的队列中执行。

如果一切正常,调度器会以上述的那种方式顺畅地运行,但这个世界没这么美好,总有意外发生,以下分析goroutine在两种例外情况下的行为。

Go runtime会在下面的goroutine被阻塞的情况下运行另外一个goroutine:

用户态阻塞/唤醒

当goroutine因为channel操作或者network I/O而阻塞时(实际上golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G,这里仅仅是举个栗子),对应的G会被放置到某个wait队列(如channel的waitq),该G的状态由_Gruning变为_Gwaitting,而M会跳过该G尝试获取并执行下一个G,如果此时没有可运行的G供M运行,那么M将解绑P,并进入sleep状态;当阻塞的G被另一端的G2唤醒时(比如channel的可读/写通知),G被标记为,尝试加入G2所在P的runnext(runnext是线程下一个需要执行的 Goroutine。), 然后再是P的本地队列和全局队列。

系统调用阻塞

当M执行某一个G时候如果发生了阻塞操作,M会阻塞,如果当前有一些G在执行,调度器会把这个线程M从P中摘除,然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P。当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

队列轮转

可见每个P维护着一个包含G的队列,不考虑G进入系统调用或IO操作的情况下,P周期性的将G调度到M中执行,执行一小段时间,将上下文保存下来,然后将G放到队列尾部,然后从队列中重新取出一个G进行调度。

除了每个P维护的G队列以外,还有一个全局的队列,每个P会周期性地查看全局队列中是否有G待运行并将其调度到M中执行,全局队列中G的来源,主要有从系统调用中恢复的G。之所以P会周期性地查看全局队列,也是为了防止全局队列中的G被饿死。

除了每个P维护的G队列以外,还有一个全局的队列,每个P会周期性地查看全局队列中是否有G待运行并将其调度到M中执行,全局队列中G的来源,主要有从系统调用中恢复的G。之所以P会周期性地查看全局队列,也是为了防止全局队列中的G被饿死。

M0

M0是启动程序后的编号为0的主线程,这个M对应的实例会在全局变量rutime.m0中,不需要在heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其他的M一样了

G0

G0是每次启动一个M都会第一个创建的goroutine,G0仅用于负责调度G,G0不指向任何可执行的函数,每个M都会有一个自己的G0,在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0

一个G由于调度被中断,此后如何恢复?

中断的时候将寄存器里的栈信息,保存到自己的G对象里面。当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了。

我这里只是根据自己的理解进行了简单的介绍,想要详细了解有关GMP的底层原理可以去看Go调度器 G-P-M 模型的设计者的文档或直接看源码

参考: ()

()


网页名称:go语言全局变量命名规范,go语言定义变量
当前路径:http://pwwzsj.com/article/hohpsp.html