Go语言实战--笔记3

互斥锁
//展示如何使用互斥锁来定义一段
//需要同步访问的代码临界区资源的同步访问
package main
import (
   "fmt"
   "runtime"
   "sync"
)
var (
   //counter 是所有goroutine都要增加其值的变量
   counter int
   //wg 用来等待程序结束
   wg sync.WaitGroup
   //mutex 用来定义一段代码的临界区
   mutex sync.Mutex
)
//main 是go程序入口
func main()  {
   //计数加2,表示要等待两个goroutine
   wg.Add(2)
   //创建两个goroutine
   go incCounter(1)
   go incCounter(2)
   //等待goroutine结束
   wg.Wait()
   fmt.Printf("Final Counter: %d\\n",counter)
}
//incCounter 使用互斥锁来同步并保证安全访问
//增加包里counter变量的值
func incCounter(id int) {
   //在函数退出时调用Done来通知main函数工作完成
   defer wg.Done()
   for count := 0;count <2;count++ {
      //同一时刻只允许一个goroutine进入这个临界区
      mutex.Lock()
      {
         //捕获counter的值
         value := counter
         //当前goroutine从线程退出,并放回队列
         runtime.Gosched()
         //增加本地value变量的值
         value++
         //将该值保存回counter
         counter = value
      }
      mutex.Unlock()
      //释放锁,允许其他正在等待的goroutine进入临界区
   }
}


通道
通过通道可以共享内置类型,命名类型,结构类型,和引用类型的值或指针
使用make创建通道
//无缓冲通道
umbuggered := make(chan int)
//有缓冲的字符串通道
buffered := make(chan string,10)

向通道发送值,接收值
//创建有缓冲的字符串通道
buffered := make(chan string,10)
//通过通道发送一个字符串
buffered <- "Gopher"
//从通道接收一个字符串
value := <-buffered

无缓冲通道
在接收前没有能力保存任何值得通道
要求发送goroutine和接收goroutine同时进行才能完成发送和接收操作
示例:
//展示使用无换从得通道来模拟2个goroutine见得网球比赛
package main
import (
   "fmt"
   "math/rand"
   "sync"
   "time"
)
//wg 用来等待程序结束
var wg sync.WaitGroup
func init()  {
   rand.Seed(time.Now().UnixNano())
}
//main 是程序得入口
func main()  {
   //创建一个无缓冲的通道
   court := make(chan int)
   //计数加2,表示要等待2个goroutine
   wg.Add(2)
   //启动两个选手
   go player("Nadal",court)
   go player("Djokovic",court)
   //发球
   court <- 1
   //等待游戏结束
   wg.Wait()
}
//player 模拟一个选手在打网球
func player(name string,court chan int) {
   //在函数退出时调用Done来通知main函数工作已经完成
   defer wg.Done()
   for {
      //等待球被击打过来
      ball,ok := <-court
      if !ok {
         //如果通道被关闭,我们就赢了
         fmt.Printf("Player %s Won\n",name)
         return
      }
      //选随机数,然后用这个数来判断我们是否丢球
      n := rand.Intn(100)
      if n % 13 == 0 {
         fmt.Printf("Player %s Missed\n",name)
         //关闭通道,表示我们输了
         close(court)
         return
      }
      //显示击球数,并将击球数加1
      fmt.Printf("Player %s Hit %d\n",name,ball)
      ball++
      //将球打向对手
      court <- ball
   }
}

//展示使用无缓冲通道来模拟4个goroutine间的接力比赛
package main
import (
   "fmt"
   "sync"
   "time"
)
//wg 用来等待程序结束
var wg sync.WaitGroup
//main 是程序的入口
func main()  {
   //创建一个无缓冲的通道
   baton := make(chan int)
   //为最后一位跑步者将计数加1
   wg.Add(1)
   //第一位跑步者持有接力棒
   go Runner(baton)
   //开始比赛
   baton <- 1
   //等待比赛结束
   wg.Wait()
}
//Runner模拟接力比赛中的一位跑步者
func Runner(baton chan int)  {
   var newRunner int
   //等待接力棒
   runner := <-baton
   //开始绕着跑道跑步
   if runner != 4 {
      newRunner = runner + 1
      fmt.Printf("Runner %d To The Line\n",newRunner)
      go Runner(baton)
   }
   //围绕跑道跑
   time.Sleep(100 * time.Millisecond)
   //比赛结束了吗?
   if runner == 4 {
      fmt.Printf("Runner %d Finished,Race Over\n", runner)
      wg.Done()
      return
   }
   //将接力棒交给下一位跑步者
   fmt.Printf("Runner %d Exchange With Runner %d\n",
      runner,
      newRunner)
   baton <- newRunner
}


有缓冲通道
被接收前能存储一个或多个值得通道
示例:
//展示使用有缓冲通道和固定数目的goroutine来处理工作
package main
import (
   "fmt"
   "math/rand"
   "sync"
   "time"
)
const (
   numberGoroutines = 4 //要使用goroutine的数量
   taskLoad = 10 //要处理的工作数量
)
//wg 用来等待程序完成
var wg sync.WaitGroup
//init 初始化包,go语言运行时会在其他代码执行之前
//优先执行这个函数
func init() {
   //初始化随机数种子
   rand.Seed(time.Now().Unix())
}
//main 是程序入口
func main()  {
   //创建一个有缓冲的通道来管理工作
   tasks := make(chan string,taskLoad)
   //启动goroutine来处理工作
   wg.Add(numberGoroutines)
   for gr := 1;gr <= numberGoroutines;gr++ {
      go worker(tasks,gr)
   }
   //增加一组要完成的工作
   for post := 1;post <= taskLoad; post++ {
      tasks <- fmt.Sprintf("Task : %d",post)
   }
   //当所有的工作都处理完成时关闭通道
   //以便所有的goroutine退出
   close(tasks)
   //等待所有工作完成
   wg.Wait()
}
//worker 作为goroutine启动来处理
//从有缓冲的通道传入的工作
func worker(tasks chan string,worker int) {
   //通知函数已经返回
   defer wg.Done()
   for {
      //等待分配工作
      task,ok := <-tasks
      if !ok {
         //这意味通道已经空了,并且已被关闭
         fmt.Printf("Worker: %d : Shutting Down\n",worker)
         return
      }
      //显示开始工作
      fmt.Printf("Worker : %d : Started %s\n",worker,task)
      //随机等一段时间来模拟工作
      sleep := rand.Int63n(100)
      time.Sleep(time.Duration(sleep)*time.Millisecond)
      //显示完成工作
      fmt.Printf("Worker: %d : Completed %s\n",worker,task)
   }
}


并发模式
runner


pool


work


标准库
文档与源代码

记录日志
log包
示例:
// 这个示例程序展示如何使用最基本的 log 包
package main
import (
   "log"
)
func init() {
   log.SetPrefix("TRACE: ")
   log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
   // Println 写到标准日志记录器
   log.Println("message")
   // Fatalln 在调用 Println()之后会接着调用 os.Exit(1)
   log.Fatalln("fatal message")
   // Panicln 在调用 Println()之后会接着调用 panic()
   log.Panicln("panic message")
}

定制的日志记录器
示例:
//展示如何创建定制的日志记录器
package main
import (
   "io"
   "io/ioutil"
   "log"
   "os"
)
var (
   Trace  *log.Logger    //记录所有日志
   Info   *log.Logger    //重要的信息
   Warning    *log.Logger    //需要注意的信息
   Error  *log.Logger    //菲常严重的问题
)
func init()  {
   file,err := os.OpenFile("errors.txt",
      os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
   if err != nil {
      log.Fatalln("Fail to open error log file:",err)
   }
   Trace = log.New(ioutil.Discard,
      "TRACE ",
      log.Ldate|log.Ltime|log.Lshortfile)
   Info = log.New(os.Stdout,
      "INFO: ",
      log.Ldate|log.Ltime|log.Lshortfile)
   Warning = log.New(os.Stdout,
      "WARING: ",
      log.Ldate|log.Ltime|log.Lshortfile)
   Error = log.New(io.MultiWriter(file,os.Stderr),
      "ERROR: ",
      log.Ldate|log.Ltime|log.Lshortfile)
}
func main()  {
   Trace.Println("I have something standard to say")
   Info.Println("Special Information")
   Warning.Println("There is someting you need to know about")
   Error.Println("Someting has faild")
}


编码/解码
解码JSON
示例:
//展示使用json包和NewDecoder函数来解码JSON响应
package main
import (
   "encoding/json"
   "fmt"
   "log"
   "net/http"
)
type (
   //gResult 映射到从搜索拿到的结果文档
   gResult struct {
      GsearchResultClass string `json:"GsearchResultClass"`
      UnescapedURL   string `json:"unescapedUrl"`
      URL    string `json:"url"`
      VisibleURL string `json:"VisibleUrl"`
      CacheURL   string `json:"cacheUrl"`
      Title  string `json:"title"`
      TitleNoFormatting  string `json:"titleNoFormatting"`
      Content    string `json:"content"`
   }
   //gResponse 包含顶级的文档
   gResponse struct {
      ResponseData struct{
         Results []gResult `json:"results"`
      } `json:"responseData"`
   }
)
func main()  {
   uri := "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=8&q=golang"
   //向Google 发起搜索
   resp,err := http.Get(uri)
   if err != nil {
      log.Println("ERROR:",err)
      return
   }
   defer resp.Body.Close()
   //将JSON 响应解码到结构类型
   var gr gResponse
   err = json.NewDecoder(resp.Body).Decode(&gr)
   if err != nil {
      log.Println("ERROR:",err)
      return
   }
   fmt.Println(gr)
}

//展示如何解码JSON字符串
package main
import (
   "encoding/json"
   "fmt"
   "log"
)
//Contact 结构代表JSON字符串
type Contact struct {
   Name   string `json:"name"`
   Title  string `json:"title"`
   Contact    struct {
      Home   string `json:"home"`
      Cell   string `json:"cell"`
   } `json:"contact"`
}
//JSON 包含用于反序列化的演示字符串
var JSON = `{
   "name": "Gopher",
   "title": "programmer",
   "contact": {
      "home": "415.333.3333",
      "cell": "415.555.5555"
   }
}`
func main()  {
   //将JSON字符串反序列化到变量
   var c Contact
   err := json.Unmarshal([]byte(JSON),&c)
   if err != nil {
      log.Println("ERROR:",err)
      return
   }
   fmt.Println(c)
}

//将JSON文档解码到一个map变量中
//展示解码JSON字符串
package main
import (
   "encoding/json"
   "fmt"
   "log"
)
// JSON 包含要反序列化的样例字符串
var JSON = `{
   "name": "Gopher",
   "title": "programmer",
   "contact": {
      "home": "415.333.3333",
      "cell": "415.555.5555"
   }
}`
func main() {
   // 将 JSON 字符串反序列化到 map 变量
   var c map[string]interface{}
   err := json.Unmarshal([]byte(JSON), &c)
   if err != nil {
      log.Println("ERROR:", err)
      return
   }
   fmt.Println("Name:", c["name"])
   fmt.Println("Title:", c["title"])
   fmt.Println("Contact")
   fmt.Println("H:", c["contact"].(map[string]interface{})["home"])
   fmt.Println("C:", c["contact"].(map[string]interface{})["cell"])
}


编码JSON
将Go语言的map类型的值或者结构类型的值转换为易读格式的json文档
示例:
//将map类型转换为JSON字符串
//展示序列化JSON字符串
package main
import (
   "encoding/json"
   "fmt"
   "log"
)
func main() {
   // 创建一个保存键值对的映射
   c := make(map[string]interface{})
   c["name"] = "Gopher"
   c["title"] = "programmer"
   c["contact"] = map[string]interface{}{
      "home": "415.333.3333",
      "cell": "415.555.5555",
   }
   // 将这个映射序列化到 JSON 字符串
   data, err := json.MarshalIndent(c, "", " ")
   if err != nil {
      log.Println("ERROR:", err)
      return
   }
   fmt.Println(string(data))
}


输入和输出
Writer 和 Reader 接口

简单的 curl
示例:
// 这个示例程序展示如何使用 io.Reader 和 io.Writer 接口
// 写一个简单版本的 curl
package main
import (
   "io"
   "log"
   "net/http"
   "os"
)
// main 是应用程序的入口
func main() {
   // 这里的 r 是一个响应,r.Body 是 io.Reader
   //r, err := http.Get(os.Args[1])
   r, err := http.Get(`https://blog.csdn.net/liao__ran?spm=1000.2115.3001.5343`)
   if err != nil {
      log.Fatalln(err)
   }
   // 创建文件来保存响应内容
   //file, err := os.Create(os.Args[2])
   file, err := os.Create(`.\a.txt`)
   if err != nil {
      log.Fatalln(err)
   }
   defer file.Close()
   // 使用 MultiWriter,这样就可以同时向文件和标准输出设备
   // 进行写操作
   dest := io.MultiWriter(os.Stdout, file)
   // 读出响应的内容,并写到两个目的地
   io.Copy(dest, r.Body)
   if err := r.Body.Close(); err != nil {
      log.Println(err)
   }
}


 测试和性能
单元测试
测试包或者程序的一部分代码或者一组代码的函数
基础单元测试
示例:
// 这个示例程序展示如何写基础单元测试
package listing01
import (
   "net/http"
   "testing"
)
const checkMark = "\u2713"
const ballotX = "\u2717"
// TestDownload 确认 http 包的 Get 函数可以下载内容
func TestDownload(t *testing.T) {
   url := "http://www.goinggo.net/feeds/posts/default?alt=rss"
   statusCode := 200
   t.Log("Given the need to test downloading content.")
   {
      t.Logf("\tWhen checking \"%s\" for status code \"%d\"",
         url, statusCode)
      {
         resp, err := http.Get(url)
         if err != nil {
            t.Fatal("\t\tShould be able to make the Get call.",
               ballotX, err)
         }
         t.Log("\t\tShould be able to make the Get call.",
            checkMark)
         defer resp.Body.Close()
         if resp.StatusCode == statusCode {
            t.Logf("\t\tShould receive a \"%d\" status. %v",
               statusCode, checkMark)
         } else {
            t.Errorf("\t\tShould receive a \"%d\" status. %v %v",
               statusCode, ballotX, resp.StatusCode)
         }
      }
   }
}
测试结果:
D:\Go\Go学习\go语言实战\项目测试>go test -v
=== RUN   TestDownload
--- FAIL: TestDownload (1.58s)
    listing01_test.go:13: Given the need to test downloading content.
    listing01_test.go:15:       When checking "http://www.goinggo.net/feeds/posts/default?alt=rss" for status code "200"
    listing01_test.go:23:               Should be able to make the Get call. ✓
    listing01_test.go:30:               Should receive a "200" status. ✗ 404
FAIL
exit status 1
FAIL    _/D_/Go/Go学习/go语言实战/项目测试      1.644s


表组测试
示例:
// 这个示例程序展示如何写一个基本的表组测试
package listing08

import (
   "net/http"
   "testing"
)

const checkMark = "\u2713"
const ballotX = "\u2717"

// TestDownload 确认 http 包的 Get 函数可以下载内容
// 并正确处理不同的状态
func TestDownload(t *testing.T) {
   var urls = []struct {
      url        string
      statusCode int
   }{
      {
         "http://www.goinggo.net/feeds/posts/default?alt=rss",
         http.StatusOK,
      },
      {
         "http://rss.cnn.com/rss/cnn_topstbadurl.rss",
         http.StatusNotFound,
      },
   }

   t.Log("Given the need to test downloading different content.")
   {
      for _, u := range urls {
         t.Logf("\tWhen checking \"%s\" for status code \"%d\"",
            u.url, u.statusCode)
         {
            resp, err := http.Get(u.url)
            if err != nil {
               t.Fatal("\t\tShould be able to Get the url.",
                  ballotX, err)
            }
            t.Log("\t\tShould be able to Get the url",
               checkMark)

            defer resp.Body.Close()

            if resp.StatusCode == u.statusCode {
               t.Logf("\t\tShould have a \"%d\" status. %v",
                  u.statusCode, checkMark)
            } else {
               t.Errorf("\t\tShould have a \"%d\" status %v %v",
                  u.statusCode, ballotX, resp.StatusCode)
            }
         }
      }
   }
}
D:\Go\Go学习\go语言实战\项目测试>go test -v listing01_test.go
=== RUN   TestDownload
--- FAIL: TestDownload (9.75s)
    listing01_test.go:29: Given the need to test downloading different content.
    listing01_test.go:32:       When checking "http://www.goinggo.net/feeds/posts/default?alt=rss" for status code "200"
    listing01_test.go:40:               Should be able to Get the url ✓
    listing01_test.go:49:               Should have a "200" status ✗ 404
    listing01_test.go:32:       When checking "http://rss.cnn.com/rss/cnn_topstbadurl.rss" for status code "404"
    listing01_test.go:37:               Should be able to Get the url. ✗ Get http://rss.cnn.com/rss/cnn_topstbadurl.rss: read tcp 192.168.92.16:60817->172.217.160.83:80: wsarecv: An existing connection was forcibly closed by the remote host.
FAIL
FAIL    command-line-arguments  9.819s
FAIL


模仿调用
示例:
// 这个示例程序展示如何内部模仿 HTTP GET 调用
// 与本书之前的例子有些差别
package listing12

import (
   "fmt"
   "net/http"
   "net/http/httptest"
)

const checkMark = "\u2713"
const ballotX = "\u2717"

// feed 模仿了我们期望接收的 XML 文档
var feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss>
<channel>
   <title>Going Go Programming</title>
   <description>Golang : https://github.com/goinggo</description>
   <link>http://www.goinggo.net/</link>
<item>
   <pubDate>Sun, 15 Mar 2015 15:04:00 +0000</pubDate>
   <title>Object Oriented Programming Mechanics</title>
   <description>Go is an object oriented language.</description>
   <link>http://www.goinggo.net/2015/03/object-oriented</link>
</item>
</channel>
</rss>`

// mockServer 返回用来处理请求的服务器的指针
func mockServer() *httptest.Server {
   f := func(w http.ResponseWriter, r *http.Request) {
      w.WriteHeader(200)
      w.Header().Set("Content-Type", "application/xml")
      fmt.Fprintln(w, feed)
   }

   return httptest.NewServer(http.HandlerFunc(f))
}
D:\Go\Go学习\go语言实战\项目测试>go test -v listing01_test.go
testing: warning: no tests to run
PASS
ok      command-line-arguments  0.056s [no tests to run]


测试服务端点

基准测试
测试代码性能的方法
可以用来识别某段代码的cpu或者内存效率问题
可以测试不同的并发模式
辅助配置工作池的数量,保证能最大话系统的吞吐量

猜你喜欢

转载自blog.csdn.net/liao__ran/article/details/114397356