在实习中使用 micro 框架,但是挺多不懂的,看了部分源码搞懂了一些,还是有一些比较复杂没搞懂。

第一部分:初始化 service 并修改端口

main.go

  1. // waitgroup is a handler wrapper which adds a handler to a sync.WaitGroup
  2. func waitgroup(wg *util.WaitGroupWrapper) server.HandlerWrapper {
  3. return func(h server.HandlerFunc) server.HandlerFunc {
  4. return func(ctx context.Context, req server.Request, rsp interface{}) error {
  5. wg.Add(1)
  6. defer wg.Done()
  7. return h(ctx, req, rsp)
  8. }
  9. }
  10. }
  11. func main() {
  12. // ...
  13. var wg util.WaitGroupWrapper
  14. var service micro.Service
  15. // 这里在下面给出解释
  16. service = micro.NewService(
  17. // 这里返回了四个函数闭包,同时也是Option类型,NewService的参数是可变长度的Option数组
  18. micro.Name("xxx"),
  19. micro.WrapHandler(waitgroup(&wg)),
  20. micro.RegisterTTL(30*time.Second),
  21. micro.RegisterInterval(10*time.Second),
  22. )
  23. // 先对 service 初始化
  24. service.Init()
  25. // 因为在 go run *.go 的时候回传入参数 --server_address=":8880" 这个时候可以这样将传入的参数取出,之后给webservice使用
  26. addr := service.Server().Options().Address
  27. // 将这个的端口赋为默认地址,即:0
  28. service.Server().Init(server.Address(server.DefaultAddress))
  29. // ...
  30. }

go-micro.go

  1. type Service interface {
  2. Init(...Option)
  3. Options() Options
  4. Client() client.Client
  5. Server() server.Server
  6. Run() error
  7. String() string
  8. }
  9. type Option func(*Options)
  10. // NewService creates and returns a new Service based on the packages within.
  11. func NewService(opts ...Option) Service {
  12. return newService(opts...)
  13. }

这里定义了一个 function type ,一开始对这个不太熟悉,看了 这篇文章 之后懂了点。定义这个 Option 之后的初始化操作就是将 Option 当做参数,传入到 NewXXX 中,在这些 NewXXX 方法又会遍历传入的 []Option 数组,然后调用这个 Option 在里面套一个 Init() 方法或者直接对变量进行赋值来完成对参数 *Options 的部分变量的初始化。

service.go

  1. func newService(opts ...Option) Service {
  2. // newOptions 的操作在下面的 options.go 文件中
  3. options := newOptions(opts...)
  4. // 包装client,添加一些信息
  5. options.Client = &clientWrapper{
  6. options.Client,
  7. metadata.Metadata{
  8. HeaderPrefix + "From-Service": options.Server.Options().Name,
  9. },
  10. }
  11. return &service{
  12. opts: options,
  13. }
  14. }
  15. type service struct {
  16. opts Options
  17. once sync.Once
  18. }
  19. func (s *service) Init(opts ...Option) {
  20. // process options
  21. for _, o := range opts {
  22. o(&s.opts)
  23. }
  24. // 能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次。
  25. s.once.Do(func() {
  26. // save user action
  27. action := s.opts.Cmd.App().Action
  28. // 下面注释给出 cmd.App().Action 的初始化
  29. // func newCmd(opts ...Option) Cmd {
  30. // options := Options{
  31. // Broker: &broker.DefaultBroker,
  32. // Client: &client.DefaultClient,
  33. // Registry: &registry.DefaultRegistry,
  34. // Server: &server.DefaultServer,
  35. // Selector: &selector.DefaultSelector,
  36. // Transport: &transport.DefaultTransport,
  37. // Brokers: DefaultBrokers,
  38. // Clients: DefaultClients,
  39. // Registries: DefaultRegistries,
  40. // Selectors: DefaultSelectors,
  41. // Servers: DefaultServers,
  42. // Transports: DefaultTransports,
  43. // }
  44. // ...
  45. // cmd := new(cmd)
  46. // cmd.opts = options
  47. // cmd.app = cli.NewApp()
  48. // ...
  49. // cmd.app.Action = func(c *cli.Context) {} 这里
  50. // ...
  51. // return cmd
  52. // set service action
  53. s.opts.Cmd.App().Action = func(c *cli.Context) {
  54. // set register interval
  55. if i := time.Duration(c.GlobalInt("register_interval")); i > 0 {
  56. s.opts.RegisterInterval = i * time.Second
  57. }
  58. // user action
  59. action(c)
  60. }
  61. // Initialise the command flags, overriding new service
  62. _ = s.opts.Cmd.Init(
  63. cmd.Broker(&s.opts.Broker),
  64. cmd.Registry(&s.opts.Registry),
  65. cmd.Transport(&s.opts.Transport),
  66. cmd.Client(&s.opts.Client),
  67. cmd.Server(&s.opts.Server),
  68. )
  69. })
  70. }

options.go

  1. func newOptions(opts ...Option) Options {
  2. opt := Options {
  3. Broker: broker.DefaultBroker,
  4. Cmd: cmd.DefaultCmd,
  5. Client: client.DefaultClient,
  6. Server: server.DefaultServer,
  7. Registry: registry.DefaultRegistry,
  8. Transport: transport.DefaultTransport,
  9. Context: context.Background(),
  10. }
  11. // 第一个值是下标,第二个值(o)是相当于value,这里是一个Option类型的函数。主要就是对 opt 赋值初始化
  12. for _, o := range opts {
  13. o(&opt)
  14. }
  15. return opt
  16. }
  17. // 下面四个函数的返回一个闭包,然后在 newOptions 的时候调用完成初始化操作
  18. // Name of the service
  19. func Name(n string) Option {
  20. return func(o *Options) {
  21. o.Server.Init(server.Name(n))
  22. }
  23. }
  24. // RegisterTTL specifies the TTL to use when registering the service
  25. func RegisterTTL(t time.Duration) Option {
  26. return func(o *Options) {
  27. o.Server.Init(server.RegisterTTL(t))
  28. }
  29. }
  30. // RegisterInterval specifies the interval on which to re-register
  31. func RegisterInterval(t time.Duration) Option {
  32. return func(o *Options) {
  33. o.RegisterInterval = t
  34. }
  35. }
  36. // WrapHandler adds a handler Wrapper to a list of options passed into the server
  37. func WrapHandler(w ...server.HandlerWrapper) Option {
  38. return func(o *Options) {
  39. var wrappers []server.Option
  40. for _, wrap := range w {
  41. wrappers = append(wrappers, server.WrapHandler(wrap))
  42. }
  43. // Init once
  44. // 根据这些option初始化server
  45. o.Server.Init(wrappers...)
  46. }
  47. }

第二部分:订阅Topic

main.go

  1. func subEvent(ctx context.Context, stats *proto.Event) error {
  2. golog.Logf("Received event %+v\n", stats)
  3. return nil
  4. }
  5. func main() {
  6. // ...
  7. // subEvent是自定义函数
  8. micro.RegisterSubscriber("TOPIC", service.Server(), subEvent)
  9. // ...
  10. }

go-micro.go

  1. func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
  2. // 调用 server.go 的函数
  3. return s.Subscribe(s.NewSubscriber(topic, h, opts...))
  4. }

server.go

  1. var (
  2. DefaultServer Server = newRpcServer()
  3. )
  4. func NewSubscriber(topic string, h interface{}, opts ...SubscriberOption) Subscriber {
  5. return DefaultServer.NewSubscriber(topic, h, opts...)
  6. }
  7. func Subscribe(s Subscriber) error {
  8. // 这里的 DefaultServer 是 RpcServer,调用下面 rpc_server.go 的函数
  9. return DefaultServer.Subscribe(s)
  10. }

rpc_server.go

  1. // 调用 subscriber.go 中的函数
  2. func (s *rpcServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
  3. return newSubscriber(topic, sb, opts...)
  4. }

subscriber.go

  1. type handler struct {
  2. method reflect.Value
  3. reqType reflect.Type
  4. ctxType reflect.Type
  5. }
  6. type subscriber struct {
  7. topic string
  8. rcvr reflect.Value
  9. typ reflect.Type
  10. subscriber interface{}
  11. handlers []*handler
  12. endpoints []*registry.Endpoint
  13. opts SubscriberOptions
  14. }
  15. func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
  16. // 利用传进来的 SubcriberOption 初始化,这个和第一部分的类似,略过
  17. var options SubscriberOptions
  18. for _, o := range opts {
  19. o(&options)
  20. }
  21. var endpoints []*registry.Endpoint
  22. var handlers []*handler
  23. // 利用反射去对传进来的 interface{} 操作,我在前面最开始传的是 subEvent 函数
  24. if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
  25. // 因为我传入的是 Func 类型,所以会进入这里
  26. h := &handler{
  27. // 获取方法
  28. method: reflect.ValueOf(sub),
  29. }
  30. // 判断参数个数,并分别给对应位置的类型赋值
  31. switch typ.NumIn() {
  32. case 1:
  33. h.reqType = typ.In(0)
  34. case 2:
  35. h.ctxType = typ.In(0)
  36. h.reqType = typ.In(1)
  37. }
  38. handlers = append(handlers, h)
  39. endpoints = append(endpoints, &registry.Endpoint{
  40. Name: "Func",
  41. Request: extractSubValue(typ),
  42. Metadata: map[string]string{
  43. "topic": topic,
  44. "subscriber": "true",
  45. },
  46. })
  47. } else {
  48. hdlr := reflect.ValueOf(sub)
  49. name := reflect.Indirect(hdlr).Type().Name()
  50. for m := 0; m < typ.NumMethod(); m++ {
  51. method := typ.Method(m)
  52. h := &handler{
  53. method: method.Func,
  54. }
  55. switch method.Type.NumIn() {
  56. case 2:
  57. h.reqType = method.Type.In(1)
  58. case 3:
  59. h.ctxType = method.Type.In(1)
  60. h.reqType = method.Type.In(2)
  61. }
  62. handlers = append(handlers, h)
  63. endpoints = append(endpoints, &registry.Endpoint{
  64. Name: name + "." + method.Name,
  65. Request: extractSubValue(method.Type),
  66. Metadata: map[string]string{
  67. "topic": topic,
  68. "subscriber": "true",
  69. },
  70. })
  71. }
  72. }
  73. return &subscriber{
  74. rcvr: reflect.ValueOf(sub),
  75. typ: reflect.TypeOf(sub),
  76. topic: topic,
  77. subscriber: sub,
  78. handlers: handlers,
  79. endpoints: endpoints,
  80. opts: options,
  81. }
  82. }

rpc_server.go

  1. func (s *rpcServer) Subscribe(sb Subscriber) error {
  2. sub, ok := sb.(*subscriber)
  3. if !ok {
  4. return fmt.Errorf("invalid subscriber: expected *subscriber")
  5. }
  6. if len(sub.handlers) == 0 {
  7. return fmt.Errorf("invalid subscriber: no handler functions")
  8. }
  9. // 验证是否合法
  10. if err := validateSubscriber(sb); err != nil {
  11. return err
  12. }
  13. s.Lock()
  14. defer s.Unlock()
  15. _, ok = s.subscribers[sub]
  16. if ok {
  17. return fmt.Errorf("subscriber %v already exists", s)
  18. }
  19. // 置为nil,nil也是值
  20. s.subscribers[sub] = nil
  21. return nil
  22. }

第三部分:运行 service

main.go

  1. // 启动订阅服务
  2. if err := service.Run(); err != nil {
  3. log.Fatal(err)
  4. }

调用 service.goRun() 方法。

service.go

  1. func (s *service) Run() error {
  2. if err := s.Start(); err != nil {
  3. return err
  4. }
  5. // start reg loop
  6. ex := make(chan bool)
  7. // 看下面
  8. go s.run(ex)
  9. ch := make(chan os.Signal, 1)
  10. signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
  11. select {
  12. // wait on kill signal
  13. case <-ch:
  14. // wait on context cancel
  15. case <-s.opts.Context.Done():
  16. }
  17. // exit reg loop
  18. close(ex)
  19. return s.Stop()
  20. }
  21. func (s *service) Start() error {
  22. // 遍历在开始前的需要执行的函数并执行
  23. for _, fn := range s.opts.BeforeStart {
  24. if err := fn(); err != nil {
  25. return err
  26. }
  27. }
  28. // 调用 rpc_server 的 Start()
  29. if err := s.opts.Server.Start(); err != nil {
  30. return err
  31. }
  32. // 调用 rpc_server 的 Register()
  33. if err := s.opts.Server.Register(); err != nil {
  34. return err
  35. }
  36. // 遍历在开始后的需要执行的函数并执行
  37. for _, fn := range s.opts.AfterStart {
  38. if err := fn(); err != nil {
  39. return err
  40. }
  41. }
  42. return nil
  43. }
  44. func (s *service) run(exit chan bool) {
  45. if s.opts.RegisterInterval <= time.Duration(0) {
  46. return
  47. }
  48. t := time.NewTicker(s.opts.RegisterInterval)
  49. for {
  50. select {
  51. case <-t.C:
  52. err := s.opts.Server.Register()
  53. if err != nil {
  54. log.Log("service run Server.Register error: ", err)
  55. }
  56. case <-exit:
  57. t.Stop()
  58. return
  59. }
  60. }
  61. }
  62. func (s *service) Stop() error {
  63. var gerr error
  64. for _, fn := range s.opts.BeforeStop {
  65. if err := fn(); err != nil {
  66. gerr = err
  67. }
  68. }
  69. // 取消注册
  70. if err := s.opts.Server.Deregister(); err != nil {
  71. return err
  72. }
  73. // 停止
  74. if err := s.opts.Server.Stop(); err != nil {
  75. return err
  76. }
  77. for _, fn := range s.opts.AfterStop {
  78. if err := fn(); err != nil {
  79. gerr = err
  80. }
  81. }
  82. return gerr
  83. }

Start() 方法会调用 server.goStart() 方法

server.go

  1. func (s *rpcServer) Start() error {
  2. registerDebugHandler(s)
  3. config := s.Options()
  4. // 返回一个监听该地址的 listener
  5. ts, err := config.Transport.Listen(config.Address)
  6. if err != nil {
  7. return err
  8. }
  9. log.Logf("Listening on %s", ts.Addr())
  10. s.Lock()
  11. s.opts.Address = ts.Addr()
  12. s.Unlock()
  13. // 启动一条协程接收信息,内部调用了 net 包的 Accept,看下面的 accept()
  14. go ts.Accept(s.accept)
  15. go func() {
  16. // wait for exit
  17. ch := <-s.exit
  18. // wait for requests to finish
  19. if wait(s.opts.Context) {
  20. s.wg.Wait()
  21. }
  22. // close transport listener
  23. ch <- ts.Close()
  24. // disconnect the broker
  25. config.Broker.Disconnect()
  26. }()
  27. // TODO: subscribe to cruft
  28. return config.Broker.Connect()
  29. }
  30. func (s *rpcServer) accept(sock transport.Socket) {
  31. defer func() {
  32. // close socket
  33. sock.Close()
  34. if r := recover(); r != nil {
  35. log.Log("panic recovered: ", r)
  36. log.Log(string(debug.Stack()))
  37. }
  38. }()
  39. for {
  40. var msg transport.Message
  41. // 接收信息存放到msg中
  42. if err := sock.Recv(&msg); err != nil {
  43. return
  44. }
  45. // we use this Timeout header to set a server deadline
  46. to := msg.Header["Timeout"]
  47. // we use this Content-Type header to identify the codec needed
  48. ct := msg.Header["Content-Type"]
  49. // 将原来的 Content-Type 转换为可以被 rpc 使用的 Type
  50. cf, err := s.newCodec(ct)
  51. // TODO: needs better error handling
  52. if err != nil {
  53. sock.Send(&transport.Message{
  54. Header: map[string]string{
  55. "Content-Type": "text/plain",
  56. },
  57. Body: []byte(err.Error()),
  58. })
  59. return
  60. }
  61. // 返回一个新的 codec
  62. codec := newRpcPlusCodec(&msg, sock, cf)
  63. // strip our headers
  64. hdr := make(map[string]string)
  65. for k, v := range msg.Header {
  66. hdr[k] = v
  67. }
  68. delete(hdr, "Content-Type")
  69. delete(hdr, "Timeout")
  70. ctx := metadata.NewContext(context.Background(), hdr)
  71. // set the timeout if we have it
  72. if len(to) > 0 {
  73. if n, err := strconv.ParseUint(to, 10, 64); err == nil {
  74. ctx, _ = context.WithTimeout(ctx, time.Duration(n))
  75. }
  76. }
  77. // add to wait group
  78. s.wg.Add(1)
  79. defer s.wg.Done()
  80. // TODO: needs better error handling
  81. // 里面包括了许多方法:
  82. // server.readRequest() // 从codec中读取请求
  83. // server.sendResponse() // 最后会将response的信息通过socket发送
  84. // service.call()
  85. if err := s.rpc.serveRequest(ctx, codec, ct); err != nil {
  86. log.Logf("Unexpected error serving request, closing socket: %v", err)
  87. return
  88. }
  89. }
  90. }
  91. func (s *rpcServer) Register() error {
  92. // parse address for host, port
  93. config := s.Options()
  94. var advt, host string
  95. var port int
  96. // check the advertise address first
  97. // if it exists then use it, otherwise
  98. // use the address
  99. if len(config.Advertise) > 0 {
  100. advt = config.Advertise
  101. } else {
  102. advt = config.Address
  103. }
  104. parts := strings.Split(advt, ":")
  105. if len(parts) > 1 {
  106. host = strings.Join(parts[:len(parts)-1], ":")
  107. port, _ = strconv.Atoi(parts[len(parts)-1])
  108. } else {
  109. host = parts[0]
  110. }
  111. addr, err := addr.Extract(host)
  112. if err != nil {
  113. return err
  114. }
  115. // register service
  116. node := &registry.Node{
  117. Id: config.Name + "-" + config.Id,
  118. Address: addr,
  119. Port: port,
  120. Metadata: config.Metadata,
  121. }
  122. node.Metadata["transport"] = config.Transport.String()
  123. node.Metadata["broker"] = config.Broker.String()
  124. node.Metadata["server"] = s.String()
  125. node.Metadata["registry"] = config.Registry.String()
  126. s.RLock()
  127. // Maps are ordered randomly, sort the keys for consistency
  128. // 生成 handlerList
  129. var handlerList []string
  130. for n, e := range s.handlers {
  131. // Only advertise non internal handlers
  132. if !e.Options().Internal {
  133. handlerList = append(handlerList, n)
  134. }
  135. }
  136. sort.Strings(handlerList)
  137. // 生成 subscriberList
  138. var subscriberList []*subscriber
  139. for e := range s.subscribers {
  140. // Only advertise non internal subscribers
  141. if !e.Options().Internal {
  142. subscriberList = append(subscriberList, e)
  143. }
  144. }
  145. sort.Slice(subscriberList, func(i, j int) bool {
  146. return subscriberList[i].topic > subscriberList[j].topic
  147. })
  148. var endpoints []*registry.Endpoint
  149. for _, n := range handlerList {
  150. endpoints = append(endpoints, s.handlers[n].Endpoints()...)
  151. }
  152. for _, e := range subscriberList {
  153. endpoints = append(endpoints, e.Endpoints()...)
  154. }
  155. s.RUnlock()
  156. service := &registry.Service{
  157. Name: config.Name,
  158. Version: config.Version,
  159. Nodes: []*registry.Node{node},
  160. Endpoints: endpoints,
  161. }
  162. s.Lock()
  163. registered := s.registered
  164. s.Unlock()
  165. if !registered {
  166. log.Logf("Registering node: %s", node.Id)
  167. }
  168. // create registry options
  169. rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)}
  170. if err := config.Registry.Register(service, rOpts...); err != nil {
  171. return err
  172. }
  173. // already registered? don't need to register subscribers
  174. if registered {
  175. return nil
  176. }
  177. s.Lock()
  178. defer s.Unlock()
  179. s.registered = true
  180. for sb, _ := range s.subscribers {
  181. handler := s.createSubHandler(sb, s.opts)
  182. var opts []broker.SubscribeOption
  183. if queue := sb.Options().Queue; len(queue) > 0 {
  184. opts = append(opts, broker.Queue(queue))
  185. }
  186. // 订阅
  187. sub, err := config.Broker.Subscribe(sb.Topic(), handler, opts...)
  188. if err != nil {
  189. return err
  190. }
  191. // 放入之前设为 nil 的 map 里面
  192. s.subscribers[sb] = []broker.Subscriber{sub}
  193. }
  194. return nil
  195. }

第四部分:初始化WebService

main.go

  1. // 对主页服务
  2. // 自定义的Handler函数 serveHome
  3. func serveHome(w http.ResponseWriter, r *http.Request) {
  4. log.Println(r.URL)
  5. if r.URL.Path != "/" {
  6. http.Error(w, "Not found", http.StatusNotFound)
  7. return
  8. }
  9. log.Println(r.URL.Path[1:])
  10. http.ServeFile(w, r, "./view/login.html")
  11. }
  12. func main() {
  13. // ...
  14. // 初始化操作和前面类似,略过
  15. webService := web.NewService(
  16. web.Name("xxx"),
  17. web.RegisterTTL(30*time.Second),
  18. web.RegisterInterval(10*time.Second),
  19. web.Address(addr),
  20. web.Registry(service.Options().Registry),
  21. )
  22. // 部署静态资源,看下面
  23. // http.FileServer()返回一个Handler,将 "" 目录(即当前目录)下的资源部署
  24. webService.Handle("/view/", http.FileServer(http.Dir("")))
  25. // 主页
  26. // 自定义的Handler函数 serveHome
  27. webService.HandleFunc("/", serveHome)
  28. if err := webService.Run(); err != nil {
  29. log.Fatal(err)
  30. }
  31. // ...
  32. }

service.go

  1. func (s *service) Handle(pattern string, handler http.Handler) {
  2. var seen bool
  3. for _, ep := range s.srv.Endpoints {
  4. if ep.Name == pattern {
  5. seen = true
  6. break
  7. }
  8. }
  9. if !seen {
  10. s.srv.Endpoints = append(s.srv.Endpoints, &registry.Endpoint{
  11. Name: pattern,
  12. })
  13. }
  14. // 底层调用http包的方法
  15. s.mux.Handle(pattern, handler)
  16. }
  17. func (s *service) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
  18. var seen bool
  19. for _, ep := range s.srv.Endpoints {
  20. if ep.Name == pattern {
  21. seen = true
  22. break
  23. }
  24. }
  25. if !seen {
  26. s.srv.Endpoints = append(s.srv.Endpoints, &registry.Endpoint{
  27. Name: pattern,
  28. })
  29. }
  30. s.mux.HandleFunc(pattern, handler)
  31. }

net/http/server.go

  1. type Handler interface {
  2. ServeHTTP(ResponseWriter, *Request)
  3. }
  4. // HandlerFunc 实现了 ServeHTTP 方法,因此实现了Handler接口
  5. type HandlerFunc func(ResponseWriter, *Request)
  6. // ServeHTTP calls f(w, r).
  7. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  8. f(w, r)
  9. }
  10. // HandleFunc registers the handler function for the given pattern.
  11. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  12. mux.Handle(pattern, HandlerFunc(handler))
  13. }
  14. func (mux *ServeMux) Handle(pattern string, handler Handler) {
  15. mux.mu.Lock()
  16. defer mux.mu.Unlock()
  17. if pattern == "" {
  18. panic("http: invalid pattern")
  19. }
  20. if handler == nil {
  21. panic("http: nil handler")
  22. }
  23. if _, exist := mux.m[pattern]; exist {
  24. panic("http: multiple registrations for " + pattern)
  25. }
  26. if mux.m == nil {
  27. mux.m = make(map[string]muxEntry)
  28. }
  29. mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
  30. if pattern[0] != '/' {
  31. mux.hosts = true
  32. }
  33. }

附无用Demo

sync.Once

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func main() {
  7. var once sync.Once
  8. for i := 0; i < 10; i++ {
  9. once.Do(func() {
  10. fmt.Println("once :", i)
  11. })
  12. }
  13. }
  14. 输出:
  15. once : 0

type function

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Fun func(s string)
  6. func A(s string) {
  7. fmt.Println("a : ", s)
  8. }
  9. func B(s string) {
  10. fmt.Println("b : ", s)
  11. }
  12. func C(s string) {
  13. fmt.Println("c : ", s)
  14. }
  15. func D(s string) {
  16. fmt.Println("d : ", s)
  17. }
  18. func main() {
  19. f := []Fun{A, B, C, D}
  20. for k, v := range f {
  21. fmt.Println(k)
  22. v("233")
  23. }
  24. }
  25. 输出:
  26. 0
  27. a : 233
  28. 1
  29. b : 233
  30. 2
  31. c : 233
  32. 3
  33. d : 233

Golang之mirco框架部分浅析的更多相关文章

  1. 【GoLang】golang 微服务框架 go-kit

    golang-Microservice Go kit - A toolkit for microservices kubernetes go-kit_百度搜索 Peter Bourgon谈使用Go和& ...

  2. Golang 网络爬虫框架gocolly/colly 四

    Golang 网络爬虫框架gocolly/colly 四 爬虫靠演技,表演得越像浏览器,抓取数据越容易,这是我多年爬虫经验的感悟.回顾下个人的爬虫经历,共分三个阶段:第一阶段,09年左右开始接触爬虫, ...

  3. Golang 网络爬虫框架gocolly/colly 三

    Golang 网络爬虫框架gocolly/colly 三 熟悉了<Golang 网络爬虫框架gocolly/colly一>和<Golang 网络爬虫框架gocolly/colly二& ...

  4. Golang 网络爬虫框架gocolly/colly 二 jQuery selector

    Golang 网络爬虫框架gocolly/colly 二 jQuery selector colly框架依赖goquery库,goquery将jQuery的语法和特性引入到了go语言中.如果要灵活自如 ...

  5. Golang 网络爬虫框架gocolly/colly 一

    Golang 网络爬虫框架gocolly/colly 一 gocolly是用go实现的网络爬虫框架,目前在github上具有3400+星,名列go版爬虫程序榜首.gocolly快速优雅,在单核上每秒可 ...

  6. Golang 网络爬虫框架gocolly/colly 五 获取动态数据

    Golang 网络爬虫框架gocolly/colly 五 获取动态数据 gcocolly+goquery可以非常好地抓取HTML页面中的数据,但碰到页面是由Javascript动态生成时,用goque ...

  7. [Golang] GoConvey测试框架使用指南

    GoConvey 是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性. GoConvey 网站 : http://smartystreet ...

  8. 流式处理框架storm浅析(下篇)

    本文来自网易云社区 作者:汪建伟 举个栗子 1 实现的目标 设计一个系统,来实现对一个文本里面的单词出现的频率进行统计. 2 设计Topology结构: 这是一个简单的例子,topology也非常简单 ...

  9. golang的beego框架开发时出现的问题纪录

    golang的beego框架开发时出现的问题纪录1.数据库并发时问题:[ORM]2017/02/20 23:44:05 -[Queries/default] - [FAIL / db.Query / ...

随机推荐

  1. 逻辑回归原理介绍及Matlab实现

    原文:逻辑回归原理介绍及Matlab实现 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/laobai1015/article/details/781 ...

  2. WPF进阶教程 - 使用Decorator自定义带三角形的边框

    原文:WPF进阶教程 - 使用Decorator自定义带三角形的边框 写下来,备忘. Decorator,有装饰器.装饰品的意思,很容易让人联想到设计模式里面的装饰器模式.Decorator类负责包装 ...

  3. asp .net 大文件传输配置

    asp .net config配置 需要在配置文件里面设置文件上传限定的两个属性值:maxAllowedContentLength,maxRequestLength 允许上传文件的长度,和请求的长度, ...

  4. Boost的某些库还是需要生成二进制的库的,必须安装才行,以及使用库的方法

    头文件就是库使用者最常问的问题就是“我该怎么安装Boost”,这个也是我一开始最关心的问题,Boost这点做的很好,将大部分实现都封装在头文件里,所以对于一些基本的Boost库,其实是不需要安装的,只 ...

  5. PHP 一致性Hash

    一致性HASH 好久没有写文章了,最近忙着公司的事情,也一拖再拖.这篇一致性hash是很久之前就有的一篇算法,记录一下,这周写个基于该算法的Redis中间件. HASH算法的精髓就在于打散原本杂乱无序 ...

  6. WPF:通过BitmapSource的CopyPixels和Create方法来切割图片

    原文 WPF:通过BitmapSource的CopyPixels和Create方法来切割图片 BitmapSource是WPF图像的最基本类型,它同时提供两个像素相关的方法就是CopyPixels和C ...

  7. DataGridView 中发生以下异常: System.Exception: 是 不是 Decimal 的有效值。 ---> System.FormatException: 输入字符串的格式不正确。

    其实之前我自己是没测出这个问题的,但是一放到测试的手上就出来了,原因我知道在哪里改输什么东西,但是人家不知道啊.报错如下: --------------------------- “DataGridV ...

  8. linq中不能准确按拼音排序

    在LinqToObject中,利用OrderBy/OrderByDescending, ThenBy/ThenByDescending这4个方法排序时,发现不能正确的按拼音排序,所以在排序时增加编码支 ...

  9. Mongodb Compile C++ Driver

    之前发现直接编译mongo源码中的驱动,静态库的驱动会很大,在链接使用的时候会报很多链接错误. 转而直接编译单独提供驱动源码,同样vc2008的版本也要做我的另一篇博文中修改,在这不多说,具体参见: ...

  10. 用python的curl和lxml来抓取和分析网页内容

    Curl是一个强大的URL语法的客户端,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, PO ...