要通过go实现一个应用场景:

1 建立一个websocket服务

2 维护在线用户的链接

3 推送消息和接受用户的操作

列出需求,很显然的想到了chat模型。于是研究了revel框架提供的samples/chat代码,以及基于gorilla/websocket实现的chat。

他们实现的思路比较类似,大概代码如下:

  1. package main
  2.  
  3. import (
  4. "github.com/go-martini/martini"
  5. "github.com/gorilla/websocket"
  6. "net/http"
  7. "time"
  8. )
  9.  
  10. const (
  11. readBufferSize =
  12. writeBufferSize =
  13. )
  14.  
  15. type Client struct {
  16. conn *websocket.Conn
  17. }
  18.  
  19. // 常住监听一个链接的读
  20. func (c *Client) readPump() {
  21.  
  22. for {
  23.  
  24. mstype, message, err := c.conn.ReadMessage()
  25.  
  26. // ....... 该处处理接受用户发来的数据
  27. }
  28. }
  29. //监听一个链接的写
  30. func (c *Client) writePump() {
  31.  
  32. for {
  33. c.conn.SetWriteDeadline(time.Now().Add( * time.Second))
  34.  
  35. // ....... 该处处理推送给用户的数据
  36. }
  37. }
  38.  
  39. // 基于martini实现的websocket服务
  40. func main() {
  41. m := martini.Classic()
  42. m.Get("/ws", func(res http.ResponseWriter, req *http.Request) {
  43. conn, err := websocket.Upgrade(res, req, nil, readBufferSize, writeBufferSize)
  44. if err != nil {
  45. log.Println(err)
  46. return
  47. }
  48. client := &Client{conn: conn}
  49.  
  50. go client.writePump() //建立一个goroutine
  51.  
  52. client.readPump()
  53.  
  54. })
  55. m.Run()
  56. }

上述代码只是用来简述运用go语言实现chat的大概思路,在最后会贴出原代码的出处。

通过看上面代码会发现,每个链接进来就需要2个goroutine,去维护它的读跟写。如果要是1万个链接,相应的就要有2万个常驻而不会释放的goroutine去监听。这样的话,到最后会不会只是维护链接就会耗尽服务器的cpu跟内存。因为只是接触go的时间不长,看完代码我有些质疑这样实现的效率问题。为了佐证我的想法,我查了下c基于epoll对链接读写的维护,代码如下:

  1. struct epoll_event ev, *events;
  2. for(;;) {
  3. nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待I/O事件
  4. for(n = 0; n < nfds; ++n) {
  5. if(events[n].data.fd == listener) { //如果是主socket的事件,则表示有新连接进入,需要进行新连接的处理。
  6. client = accept(listener, (struct sockaddr *) &local, &addrlen);
  7. if(client < 0){
  8. perror("accept error");
  9. continue;
  10. }
  11. setnonblocking(client); // 将新连接置于非阻塞模式
  12. ev.events = EPOLLIN | EPOLLET;
  13. //注意这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,
  14. //如果有写操作的话,这个时候epoll是不会返回事件的,
  15. //如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET。
  16. ev.data.fd = client; // 并且将新连接也加入EPOLL的监听队列
  17. if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { // 设置好event之后,将这个新的event通过epoll_ctl
  18. //加入到epoll的监听队列里,这里用EPOLL_CTL_ADD
  19. //来加一个新的 epoll事件。可以通过EPOLL_CTL_DEL来减少
  20. //一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。
  21. fprintf(stderr, "epoll set insertion error: fd=%d"0, client);
  22. return -1;
  23. }
  24. } else // 如果不是主socket的事件的话,则代表这是一个用户的socket的事件,
  25. // 则用来处理这个用户的socket的事情是,比如说read(fd,xxx)之类,或者一些其他的处理。
  26. do_use_fd(events[n].data.fd);
  27. }
  28. }

从代码层面对比来看,同是1万个链接,c基于epoll的实现只有一个常驻的线程去监听I/O事件,而go需要2万个goroutine去监听。这2种语言实现方式间会不会存在比较明显的性能差距,这是我开始学习的时候,一直存在的疑问。

不过看了这篇文章时,解除了我很多疑惑,也让我对go的goroutine有了深入一点的认识。

https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.html (goroutine的生老病死)

1 goroutine切换时的上下文信息是保存在结构体的sched域中的。goroutine是轻量级的“线程”或者称为协程,切换时变不必陷入到操作系统内核中,所以保存过程很轻量

2 goroutine创建完成完后,会放入相应的队列

3 go会建多个worker进程,worker做的事情就是不停的去任务队列中取一个任务出来执行

4 goroutine准备好的时候,会获取cpu去执行,执行完了会让出cpu然后挂起

模式大概是这样的:

  1. func M() {
  2. for {
  3. sched.lock.Lock() //互斥地从就绪G队列中取一个g出来运行
  4. if sched.allg > 0 {
  5. g := sched.allg[0]
  6. sched.allg = sched.allg[1:]
  7. sched.lock.Unlock()
  8. g.Run() //运行它
  9. } else {
  10. sched.lock.Unlock()
  11. }
  12. }
  13. }
  14. for i:=0; i<GOMAXPROCS; i++ {
  15. go M()
  16. }

上面这些是刚开始学习的一些疑惑和感悟,希望之后深入学习后,对go有更全面的认识。

参考资料:

1 https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.html

2 https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.3.html

3 http://leejia.blog.51cto.com/4356849/1021066

revel的chat代码

https://github.com/revel/samples

gorilla/websocket的chat代码

https://github.com/gorilla/websocket/tree/master/examples  

go语言从零学起(三) -- chat实现的思考的更多相关文章

  1. go语言从零学起(一) -- 文档教程篇

    先记录一下自己学go语言的出发点 作为一个phper,精通一门底层语言一直是努力的目标. 相对于c,c++,go语言不需要过多的关注指针,内存释放,一两行代码就能跑起一个server服务,简直不要太简 ...

  2. go语言从零学起(四) -- 基于martini和gorilla实现的websocket聊天实例

    如果只是想了解chat的实现方式,在gorilla和revel框架里面都有完整的chat实例可以提供参考.本篇讲解的是,如何基于martini实现websocket的聊天. 配置步骤: 1 已经安装了 ...

  3. go语言从零学起(二)--list循环删除元素(转载)

    本篇系转载 在使用go的container/list的package时,你可能会无意间踩一个小坑,那就是list的循环删除元素. list删除元素,直观写下来的代码如下: package main i ...

  4. 从零学脚手架(三)---webpack属性详解

    如果此篇对您有所帮助,在此求一个star.项目地址: OrcasTeam/my-cli 在上一篇中,介绍了webpack的entry.output.plugins属性. 在这一篇,接着介绍其它配置属性 ...

  5. 带你从零学ReactNative开发跨平台App开发(三)

    ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...

  6. 零基础,三个月内,找到??? java后端开发工作

    一.分析你的问题 出于尊重,先分析一下你的原问题吧,从您的问题,我提取到关键信息:"零基础"."三个月内"."找到工作",最后一个关键词&q ...

  7. 从头开始学JavaScript (三)——数据类型

    原文:从头开始学JavaScript (三)--数据类型 一.分类 基本数据类型:undefined.null.string.Boolean.number 复杂数据类型:object object的属 ...

  8. C语言老司机学Python (五)

    今天看的是标准库概览. 操作系统接口: 用os模块实现. 针对文件和目录管理,还有个shutil模块可以用. 例句: import os os.getcwd() # 返回当前的工作目录 os.chdi ...

  9. 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    .引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...

随机推荐

  1. 20135332 第一次JAVA实验报告

    课程:Java程序设计          班级: 1353 姓名:武西垚               学号:20135332 成绩:             指导教师:娄嘉鹏       实验日期:2 ...

  2. java读取properties文件的几种方法

    一.项目中经常会需要读取配置文件(properties文件),因此读取方法总结如下: 1.通过java.util.Properties读取 Properties p=new Properties(); ...

  3. Beta Scrum Day 1 — 听说

    听说

  4. DPDK L2fwd 源码阅读

    代码部分 /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2010-2016 Intel Corporation */ #include ...

  5. Beta阶段DAY1

    一.提供当天站立式会议照片一张 二.每个人的工作 1.讨论项目每个成员的昨天进展 刘阳航:了解了自己再beta阶段的安排,但因为考试,有心无力. 林庭亦:颜色设置的相关代码的查看. 郑子熙:和另一位组 ...

  6. HDU 2106 母猪的故事

    http://acm.hdu.edu.cn/showproblem.php?pid=2160 Problem Description 话说现在猪肉价格这么贵,著名的ACBoy 0068 也开始了养猪生 ...

  7. input accept 属性

    *.3gpp audio/3gpp, video/3gpp 3GPP Audio/Video *.ac3 audio/ac3 AC3 Audio *.asf allpication/vnd.ms-as ...

  8. python3+selenium3+requests爬取我的博客粉丝的名称

    爬取目标 1.本次代码是在python3上运行通过的 selenium3 +firefox59.0.1(最新) BeautifulSoup requests 2.爬取目标网站,我的博客:https:/ ...

  9. python自动化之邮件发送

    #!/usr/bin/env python # -*- coding:utf-8 -*- import smtplib from email.mime.multipart import MIMEMul ...

  10. BZOJ3252 攻略(贪心+dfs序+线段树)

    考虑贪心,每次选价值最大的链.选完之后对于链上点dfs序暴力修改子树.因为每个点最多被选一次,复杂度非常正确. #include<iostream> #include<cstdio& ...