Golang编程经验总结

如何选择web框架:

首先Golang语言开发web项目不一定非要框架,本身已经提供了Web开发需要的一切必要技术。当然如果想要ruby里面Rail那种高层次全栈式的MVC框架,
Golang里面暂时没有,但是不是所有人都喜欢这种复杂的框架。Golang里面一些应用层面的技术需要自己去组装,比如session,cache, log等等. 可选择的web框架有martini, goji等,都是轻量级的。

Golang的web项目中的keepalive

关于keepalive,
是比较复杂的, 注意以下几点:

  1. http1.1
    默认支持keepalive, 但是不同浏览器对keepalive都有个超时时间, 比如firefox:

    默认超时时间115秒, 不同浏览器不一样;

  2. Nginx默认超时时间75秒;
  3. golang默认超时时间是无限的,
    要控制golang中的keepalive可以设置读写超时, 举例如下:
	server := &http.Server{
		Addr:           ":9999",
		Handler:        framework,
		ReadTimeout:    32 * time.Second,
		WriteTimeout:   32 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	server.ListenAndServe()

github.com/go-sql-driver/mysql使用主意事项:

这是使用率极高的一个库,
在用它进行事务处理的情况下, 要注意一个问题, 由于它内部使用了连接池, 使用事务的时候如果没有Rollback或者Commit, 这个取出的连接就不会放回到池子里面, 导致的后果就是连接数过多, 所以使用事务的时候要注意正确地使用。

github.com/garyburd/redigo/redis使用注意事项:

这也是一个使用率极高的库,
同样需要注意,它是支持连接池的, 所以最好使用连接池, 正确的用法是这样的:

func initRedis(host string) *redis.Pool {
	return &redis.Pool{
		MaxIdle: 64,
		IdleTimeout: 60 * time.Second,
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")

			return err
		},
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", host)
			if err != nil {
				return nil, err
			}

			_, err = c.Do("SELECT", config.RedisDb)

			return c, err
		},
	}
}

另外使用的时候也要把连接放回到池子里面,
否则也会导致连接数居高不下。用完之后调用rd.Close(), 这个Close并不是真的关闭连接,而是放回到池子里面。

如何全局捕获panic级别错误:

		defer func() {
			if err := recover(); err != nil {
				lib.Log4e("Panic error", err)
			}
		}()

1. 需要注意的是捕获到pannic之后, 程序的执行点不会回到触发pannic的地方,需要程序再次执行,
一些框架支持这一点,比如martini里面有c.Next()。

2.
如果程序main里启动了多个goroutine, 每个goroutine里面都应该捕获pannic级别错误, 否则某个goroutine触发panic级别错误之后,整个程序退出, 这是非常不合理的。

最容易出错的地方:

使用指针,但是没有判断指针是否为nil,
Golang中array, struct是值语义, slice,map, chanel是引用传递。

如何获取程序执行栈:

		defer func() {
			if err := recover(); err != nil {
				var st = func(all bool) string {
					// Reserve 1K buffer at first
					buf := make([]byte, 512)

					for {
						size := runtime.Stack(buf, all)
						// The size of the buffer may be not enough to hold the stacktrace,
						// so double the buffer size
						if size == len(buf) {
							buf = make([]byte, len(buf)<<1)
							continue
						}
						break
					}

					return string(buf)
				}
				lib.Log4e("panic:" + toString(err) + "\nstack:" + st(false))
			}
		}()

具体方法就是调用
runtime.Stack。

如何执行异步任务:

比如用户提交email,
给用户发邮件, 发邮件的步骤是比较耗时的, 这个场景适合可以使用异步任务:

		result := global.ResponseResult{ErrorCode: 0, ErrorMsg: "GetInviteCode success!"}
		render.JSON(200, &result)
		go func() {
			type data struct {
				Url string
			}
			name := "beta_test"
			subject := "We would like to invite you to the private beta of Screenshot."
			url := config.HttpProto + r.Host + "/user/register/" + *uniqid
			html := ParseMailTpl(&name, &beta_test_mail_content, data{url})
			e := this.SendMail(mail, subject, html.String())
			if e != nil {
				lib.Log4w("GetInviteCode, SendMail faild", mail, uniqid, e)
			} else {
				lib.Log4w("GetInviteCode, SendMail success", mail, uniqid)
			}
		}()

思路是启动一个goroutine执行异步的操作,
当前goroutine继续向下执行。特别需要注意的是新启动的个goroutine如果对全局变量有读写操作的话,需要注意避免发生竞态条件, 可能需要加锁。

如何使用定时器:

通常情况下,
写一些定时任务需要用到crontab, 在Golang里面是不需要的, 提供了非常好用的定时器。举例如下:

func Init() {
	ticker := time.NewTicker(30 * time.Minute)
	for {
		select {
		case c := <-global.TaskCmdChannel:
			switch *c {
			case "a":
				//todo
			}
		case c := <-global.TaskImageMessageChannel:
			m := new(model.TaskModel)
			m.Init()
			m.CreateImageMessage(c)
			m = nil
		case <-ticker.C:
			m := new(model.TaskModel)
			m.Init()
			m.CleanUserExpiredSessionKey()
			m = nil
		}
	}
}

多goroutine执行如果避免发生竞态条件:

Data
races are among the most common and hardest to debug types of bugs in concurrent systems. A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write. See the The Go Memory Model for details.

官方相关说明:

http://blog.golang.org/race-detector

http://golang.org/ref/mem

多goroutine执行,访问全局的变量,比如map,可能会发生竞态条件,
如何检查呢?首先在编译的时候指定 -race参数,指定这个参数之后,编译出来的程序体积大一倍以上, 另外cpu,内存消耗比较高,适合测试环境, 但是发生竞态条件的时候会panic,有详细的错误信息。go内置的数据结构array,slice,
map都不是线程安全的。

没有设置runtime.GOMAXPROCS会有竞态条件的问题吗?

答案是没有,
因为没有设置runtime.GOMAXPROCS的情况下, 所有的goroutine都是在一个原生的系统thread里面执行, 自然不会有竞态条件。

如何充分利用CPU多核:

runtime.GOMAXPROCS(runtime.NumCPU()
* 2)

以上是根据经验得出的比较合理的设置。

解决并发情况下的竞态条件的方法:

1.
 channel, 但是channel并不能解决所有的情况,channel的底层实现里面也有用到锁, 某些情况下channel还不一定有锁高效, 另外channel是Golang里面最强大也最难掌握的一个东西, 如果发生阻塞不好调试。

2.
加锁, 需要注意高并发情况下,锁竞争也是影响性能的一个重要因素, 使用读写锁,在很多情况下更高效, 举例如下:

var mu sync.RWMutex

	…

	mu.RLock()
	defer mu.RUnlock()
	conns := h.all_connections[img_id]

	for _, c := range conns {
		if c == nil /*|| c.uid == uid */ {
			continue
		}

		select {
		case c.send <- []byte(message):
		default:
			h.conn_unregister(c)
		}
	}

使用锁有个主意的地方是避免死锁,比如循环加锁。

3. 原子操作(CAS), Golang的atomic包对原子操作提供支持,Golang里面锁的实现也是用的原子操作。

获取程序绝对路径:

Golang编译出来之后是独立的可执行程序,
不过很多时候需要读取配置,由于执行目录有时候不在程序所在目录,路径的问题经常让人头疼,正确获取绝对路径非常重要, 方法如下:

func GetCurrPath() string {
	file, _ := exec.LookPath(os.Args[0])
	path, _ := filepath.Abs(file)
	index := strings.LastIndex(path, string(os.PathSeparator))
	ret := path[:index]
	return ret
}

Golang函数默认参数:

大家都知道Golang是一门简洁的语言,
不支持函数默认参数. 这个特性有些情况下确实是有用的,如果不支持,往往需要重写函数,或者多写一个函数。其实这个问题非常好解决, 举例如下:

func (this *ImageModel) GetImageListCount(project_id int64,  paramter_optional ...int) int {
	var t int

	expire_time := 600
	if len(paramter_optional) > 0 {
		expire_time = paramter_optional[0]
	}

	...
}

性能监控:

		go func() {
			profServeMux := http.NewServeMux()
			profServeMux.HandleFunc("/debug/pprof/", pprof.Index)
			profServeMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
			profServeMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
			profServeMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
			err := http.ListenAndServe(":7789", profServeMux)
			if err != nil {
				panic(err)
			}
		}()

接下来就可以使用go
tool pprof分析。

如何进行程序调试:

对于调试,每个人理解不一样,
如果要调试程序功能, 重新编译即可, Golang的编译速度极快。如果在开发的时候调试程序逻辑, 一般用log即可, Golang里面最好用的log库是log4go, 支持log级别。如果要进行断点调试, GoEclipse之类的是支持的, 依赖Mingw和GDB, 我个人不习惯这种调试方法。

守护进程(daemon)

下面给出完整的真正可用的例子:

package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "syscall"
    "time"
)

func daemon(nochdir, noclose int) int {
    var ret, ret2 uintptr
    var err syscall.Errno

    darwin := runtime.GOOS == "darwin"

    // already a daemon
    if syscall.Getppid() == 1 {
        return 0
    }

    // fork off the parent process
    ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
    if err != 0 {
        return -1
    }

    // failure
    if ret2 < 0 {
        os.Exit(-1)
    }

    // handle exception for darwin
    if darwin && ret2 == 1 {
        ret = 0
    }

    // if we got a good PID, then we call exit the parent process.
    if ret > 0 {
        os.Exit(0)
    }

    /* Change the file mode mask */
    _ = syscall.Umask(0)

    // create a new SID for the child process
    s_ret, s_errno := syscall.Setsid()
    if s_errno != nil {
        log.Printf("Error: syscall.Setsid errno: %d", s_errno)
    }
    if s_ret < 0 {
        return -1
    }

    if nochdir == 0 {
        os.Chdir("/")
    }

    if noclose == 0 {
        f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
        if e == nil {
            fd := f.Fd()
            syscall.Dup2(int(fd), int(os.Stdin.Fd()))
            syscall.Dup2(int(fd), int(os.Stdout.Fd()))
            syscall.Dup2(int(fd), int(os.Stderr.Fd()))
        }
    }

    return 0
}

func main() {
    daemon(0, 1)
    for {
        fmt.Println("hello")
        time.Sleep(1 * time.Second)
    }

}

进程管理:

个人比较喜欢用supervisord来进行进程管理,支持进程自动重启,supervisord是一个python开发的工具,
用pip安装即可。

代码热更新:

代码热更新一直是解释型语言比较擅长的,Golang里面不是做不到,只是稍微麻烦一些,
就看必要性有多大。如果是线上在线人数很多, 业务非常重要的场景, 还是有必要, 一般情况下没有必要。

  1. 更新配置.

    因为配置文件一般是个json或者ini格式的文件,是不需要编译的,
    在线更新配置还是相对比较容易的, 思路就是使用信号, 比如SIGUSER2, 程序在信号处理函数中重新加载配置即可。

  2. 热更新代码.

    目前网上有多种第三方库, 实现方法大同小异。先编译代码(这一步可以使用fsnotify做到监控代码变化,自动编译),关键是下一步graceful
    restart进程,实现方法可参考:http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
      也是创建子进程,杀死父进程的方法。

条件编译:

条件编译时一个非常有用的特性,一般一个项目编译出一个可执行文件,但是有些情况需要编译成多个可执行文件,执行不同的逻辑,这比通过命令行参数执行不同的逻辑更清晰.比如这样一个场景,一个web项目,是常驻进程的,
但是有时候需要执行一些程序步骤初始化数据库,导入数据,执行一个特定的一次性的任务等。假如项目中有一个main.go, 里面定义了一个main函数,同目录下有一个task.go函数,里面也定义了一个main函数,正常情况下这是无法编译通过的, 会提示“main redeclared”。解决办法是使用go build 的-tags参数。步骤如下(以windows为例说明):

1.在main.go头部加上//
+build main

2.
在task.go头部加上// +build task

3.
编译住程序:go build -tags ‘main‘

4.
编译task:go build -tags ‘task‘ -o task.exe

官方说明:

Build
Constraints

A
build constraint is a line comment beginning with the directive  +build that lists the conditions under which a file should be included in the package. Constraints may appear in any kind of source file (not just Go), but they must appear near the top of the
file, preceded only by blank lines and other line comments.

To
distinguish build constraints from package documentation, a series of build constraints must be followed by a blank line.

如果将项目有关资源文件打包进主程序:

使用go
generate命令,参考godoc的实现。

与C/C++
交互

1.
Cgo,Cgo支持Golang和C/C++混编, 在Golang里面使用pthread,libuv之类的都不难,github上也有相关开源代码;

2.Swig,
很多库都用Swig实现了Golang的绑定,Swig也可以反向回调Golang代码。

3.
syscall包, 该包让你以Golang的方式进行系统编程,不需要再使用C/C++,
syscall提供了很多系统接口,比如epoll,原始socket套接字编程接口等。

其他:

近几年最热门的技术之一Docker是用Golang开发的,
已经有相关的书出版, 对系统运维,云计算感兴趣的可以了解。

时间: 2024-07-28 22:36:36

Golang编程经验总结的相关文章

前端编程经验的总结

功能界面设计,涉及到的技术有前端的CSS+DIV布局,以前布局使用表格,框架,现在布局基本上 使用区块的浮动布局. 功能界面实际的一般思路是: >1. 首先是要有项目的需求分析,完成需求说明的文档,这个部分右需求分析是完成. >2. 功能界面的设计,这部分由美工和网站策划师完成. >3. 前端工程师完成功能界面的实现,必须分毫不差的实现美工的效果. >4. 布局完成,渲染完成之后,即可以为表单添加特效,校验及其他的特效等,也就是      前端工程师的脚本编程. >5. 有些

牢记20条编程经验

http://article.yeeyan.org/view/184220/150437 原文作者乔纳森·丹尼可(JonathanDanylko)是一位自由职业的Web架构师和程序员,编程经验已超过20年 1. 估算解决问题所需要的时间.不要怕,承认吧!我曾见过一些程序员为了解决一个特殊问题而坐在显示器前面8小时.为自己定一个时间限制吧,1小时.30分钟或甚至15分钟.如果在这期间你不能解决问题,那就去寻求帮助,或到网上找答案,而不是尝试去做“超级堆码员”. 2. 编程语言是一种语言,只是一种语

Python/Numpy大数据编程经验

Python/Numpy大数据编程经验 1.边处理边保存数据,不要处理完了一次性保存.不然程序跑了几小时甚至几天后挂了,就啥也没有了.即使部分结果不能实用,也可以分析程序流程的问题或者数据的特点. 2. 及时用 del 释放大块内存.Python缺省是在变量范围(variablescope)之外才释放一个变量,哪怕这个变量在后面的代码没有再被用到,所以需要手动释放大的array. 注意所有对数组的引用都del之后,数组才会被del.这些引用包括A[2:]这样的view,即使np.split也只是

JAVA web编程经验之: 一个请求一个事务

对于一个web请求,你会开启几个事务呢? 或许你没注意过吧. 又或许你不会对代码,性能要求太高,所以.... 一个请求一个事务, 因为一个事务往往和一个数据库连接关联, 如果开启了多个事务的话,也就意味着多个数据库连接, 性能不高吧? 前提 1.项目的代码结构分层如下: web层 ->  service层 -> infrastructure层(或DAO层) 2.所有事务都添加在 service层, 通过AOP(或其他类似的技术)实现 先看代码(一个Spring Controller 的调用代码

Windows网络编程经验小结

转自:CSDN网友的强贴,其ID:gdy119 (夜风微凉) 1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket: BOOL bReuseaddr=TRUE; setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL)); 2. 如果要已经处于连接状态的soket

嵌入式C编程经验 之 全局变量猛于虎

/********************************************************************************* * Filename: 一线研发之声:嵌入式C编程经验 之 全局变量猛于虎 * Author:SedateFire          E-mail:[email protected] * Version:1.001                 Time: 2012-01-05 * key: 嵌入式  os-less  全局变量 

编程经验

所谓高手,就是说他在模仿的过程中不断比较自己写的东西和框架本身的差异,不断发现问题,想尽办法解决问题,思考得越多,你碰到的问题就会越多,这是一个正向循环,最终你的技术能力就会螺旋式的上升:而低手只会被动的等待问题,一旦问题自己觉得解决得差不多就放下了,这样自然就不会产生更多的问题,最终技术能力就始终停留在那个菜鸟阶段 1. 估算解决问题所需要的时间.不要怕,承认吧!我曾见过一些程序员为了解决一个特殊问题而坐在显示器前面8小时.为自己定一个时间限制吧,1小时.30分钟或甚至15分钟.如果在这期间你

【转载】程序员:增加编程经验的3种途径

最近,有位论坛会员陷入了一个与许多入门级程序员相同的尴尬局面中:企业们不喜欢雇佣没有经验的人,并且多数都不愿意提供培训.如果那么多的企业都不雇佣没有经验的人,那么这些人要从哪寻得他们所需的经验呢?不幸的是,这种局面是大多数IT人士所面临的主要问题. 通过与这位会员长期的交流与反复的讨论,就加快他的职业生涯发展,除了给他<开始编程生涯的5个建议>之外,我另外还提出了三条建议,以助他积累增加编程经验. 1. 无偿工作(或几乎免费) 企业界通常可能并不想雇佣经验少或没有经验的人,而非盈利界通常乐意(

C++的XML编程经验――LIBXML2库使用指南[转]

C++的XML编程经验――LIBXML2库使用指南 写这篇文章的原因有如下几点:1)C++标准库中没有操作XML的方法,用C++操作XML文件必须熟悉一种函数库,LIBXML2是其中一种很优秀的XML库,而且它同时支持多种编程语言:2)LIBXML2库的Tutorial写得不太好,尤其是编码转换的部分,不适用于中文编码的转换:3)网上的大多数关于Libxml2的介绍仅仅是翻译了自带的资料,没有详细介绍如何在windows平台下进行编程,更很少提到如何解决中文问题. 基于以上几点原因,决定写一个在