互斥锁
//展示如何使用互斥锁来定义一段
//需要同步访问的代码临界区资源的同步访问
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.youkuaiyun.com/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或者内存效率问题
可以测试不同的并发模式
辅助配置工作池的数量,保证能最大话系统的吞吐量
Go语言实战--笔记3
最新推荐文章于 2024-09-14 13:39:11 发布
本文介绍Go语言中互斥锁和通道的使用方法,展示了如何确保并发安全及实现goroutine间通信。此外,还提供了单元测试、表组测试及HTTP请求内部模拟测试的实例。

3531

被折叠的 条评论
为什么被折叠?



