访问一个网站的过程,上海开公司,wordpress 视频截图,网站平台有哪些Go 程
Go 程#xff08;goroutine#xff09;是由 Go 运行时管理的轻量级线程。
go f(x, y, z)
会启动一个新的 Go 程并执行
f(x, y, z)
f, x, y 和 z 的求值发生在当前的 Go 程中#xff0c;而 f 的执行发生在新的 Go 程中。
Go 程在相同的地址空间中运行#xff0c…Go 程
Go 程goroutine是由 Go 运行时管理的轻量级线程。
go f(x, y, z)
会启动一个新的 Go 程并执行
f(x, y, z)
f, x, y 和 z 的求值发生在当前的 Go 程中而 f 的执行发生在新的 Go 程中。
Go 程在相同的地址空间中运行因此在访问共享的内存时必须进行同步。sync 包提供了这种能力不过在 Go 中并不经常用到因为还有其它的办法见下一页。
package mainimport (fmttime
)func say(s string) {for i : 0; i 5; i {time.Sleep(100 * time.Millisecond)fmt.Println(s)}
}func main() {go say(world)say(hello)
}信道
信道是带有类型的管道你可以通过它用信道操作符 - 来发送或者接收值。
ch - v // 将 v 发送至信道 ch。
v : -ch // 从 ch 接收值并赋予 v。
“箭头”就是数据流的方向。
和映射与切片一样信道在使用前必须创建
ch : make(chan int)
默认情况下发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
以下示例对切片中的数进行求和将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算它就能算出最终的结果。
package mainimport fmtfunc sum(s []int, c chan int) {sum : 0for _, v : range s {sum v}c - sum // 将和送入 c
}func main() {s : []int{7, 2, 8, -9, 4, 0}c : make(chan int)go sum(s[:len(s)/2], c)go sum(s[len(s)/2:], c)x, y : -c, -c // 从 c 中接收fmt.Println(x, y, xy)
}带缓冲的信道
信道可以是 带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道
ch : make(chan int, 100)
仅当信道的缓冲区填满后向其发送数据时才会阻塞。当缓冲区为空时接受方会阻塞。
修改示例填满缓冲区然后看看会发生什么。
package mainimport fmtfunc main() {ch : make(chan int, 2)ch - 1ch - 2fmt.Println(-ch)fmt.Println(-ch)
}range 和 close
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭若没有值可以接收且信道已被关闭那么在执行完
v, ok : -ch
之后 ok 会被设置为 false。
循环 for i : range c 会不断从信道接收值直到它被关闭。
*注意* 只有发送者才能关闭信道而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌panic。
*还要注意* 信道与文件不同通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭例如终止一个 range 循环。
package mainimport (fmt
)func fibonacci(n int, c chan int) {x, y : 0, 1for i : 0; i n; i {c - xx, y y, xy}close(c)
}func main() {c : make(chan int, 10)go fibonacci(cap(c), c)for i : range c {fmt.Println(i)}
}select 语句
select 语句使一个 Go 程可以等待多个通信操作。
select 会阻塞到某个分支可以继续执行为止这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
package mainimport fmtfunc fibonacci(c, quit chan int) {x, y : 0, 1for {select {case c - x:x, y y, xycase -quit:fmt.Println(quit)return}}
}func main() {c : make(chan int)quit : make(chan int)go func() {for i : 0; i 10; i {fmt.Println(-c)}quit - 0}()fibonacci(c, quit)
}默认选择
当 select 中的其它分支都没有准备好时default 分支就会执行。
为了在尝试发送或者接收时不发生阻塞可使用 default 分支
select {
case i : -c:// 使用 i
default:// 从 c 中接收会阻塞时执行
}
package mainimport (fmttime
)func main() {tick : time.Tick(100 * time.Millisecond)boom : time.After(500 * time.Millisecond)for {select {case -tick:fmt.Println(tick.)case -boom:fmt.Println(BOOM!)returndefault:fmt.Println( .)time.Sleep(50 * time.Millisecond)}}
}练习等价二叉查找树
不同二叉树的叶节点上可以保存相同的值序列。例如以下两个二叉树都保存了序列 11235813。 在大多数语言中检查两个二叉树是否保存了相同序列的函数都相当复杂。 我们将使用 Go 的并发和信道来编写一个简单的解法。
本例使用了 tree 包它定义了类型
type Tree struct {Left *TreeValue intRight *Tree
} 练习等价二叉查找树
1. 实现 Walk 函数。
2. 测试 Walk 函数。
函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树它保存了值 k, 2k, 3k, ..., 10k。
创建一个新的信道 ch 并且对其进行步进
go Walk(tree.New(1), ch)
然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10。
3. 用 Walk 实现 Same 函数来检测 t1 和 t2 是否存储了相同的值。
4. 测试 Same 函数。
Same(tree.New(1), tree.New(1)) 应当返回 true而 Same(tree.New(1), tree.New(2)) 应当返回 false。
Tree 的文档可在这里找到。 package mainimport golang.org/x/tour/tree// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int)// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) boolfunc main() {
}sync.Mutex
我们已经看到信道非常适合在各个 Go 程间进行通信。
但是如果我们并不需要通信呢比如说若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量从而避免冲突
这里涉及的概念叫做 *互斥mutual*exclusion* 我们通常使用 *互斥锁Mutex* 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法
LockUnlock
我们可以通过在代码前调用 Lock 方法在代码后调用 Unlock 方法来保证一段代码的互斥执行。参见 Inc 方法。
我们也可以用 defer 语句来保证互斥锁一定会被解锁。参见 Value 方法。
package mainimport (fmtsynctime
)// SafeCounter 的并发使用是安全的。
type SafeCounter struct {v map[string]intmux sync.Mutex
}// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {c.mux.Lock()// Lock 之后同一时刻只有一个 goroutine 能访问 c.vc.v[key]c.mux.Unlock()
}// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {c.mux.Lock()// Lock 之后同一时刻只有一个 goroutine 能访问 c.vdefer c.mux.Unlock()return c.v[key]
}func main() {c : SafeCounter{v: make(map[string]int)}for i : 0; i 1000; i {go c.Inc(somekey)}time.Sleep(time.Second)fmt.Println(c.Value(somekey))
}练习Web 爬虫
在这个练习中我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
修改 Crawl 函数来并行地抓取 URL并且保证不重复。
提示你可以用一个 map 来缓存已经获取的 URL但是要注意 map 本身并不是并发安全的
package mainimport (fmt
)type Fetcher interface {// Fetch 返回 URL 的 body 内容并且将在这个页面上找到的 URL 放到一个 slice 中。Fetch(url string) (body string, urls []string, err error)
}// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {// TODO: 并行的抓取 URL。// TODO: 不重复抓取页面。// 下面并没有实现上面两种情况if depth 0 {return}body, urls, err : fetcher.Fetch(url)if err ! nil {fmt.Println(err)return}fmt.Printf(found: %s %q\n, url, body)for _, u : range urls {Crawl(u, depth-1, fetcher)}return
}func main() {Crawl(https://golang.org/, 4, fetcher)
}// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResulttype fakeResult struct {body stringurls []string
}func (f fakeFetcher) Fetch(url string) (string, []string, error) {if res, ok : f[url]; ok {return res.body, res.urls, nil}return , nil, fmt.Errorf(not found: %s, url)
}// fetcher 是填充后的 fakeFetcher。
var fetcher fakeFetcher{https://golang.org/: fakeResult{The Go Programming Language,[]string{https://golang.org/pkg/,https://golang.org/cmd/,},},https://golang.org/pkg/: fakeResult{Packages,[]string{https://golang.org/,https://golang.org/cmd/,https://golang.org/pkg/fmt/,https://golang.org/pkg/os/,},},https://golang.org/pkg/fmt/: fakeResult{Package fmt,[]string{https://golang.org/,https://golang.org/pkg/,},},https://golang.org/pkg/os/: fakeResult{Package os,[]string{https://golang.org/,https://golang.org/pkg/,},},
}