博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GO语言的进阶之路-协程和Channel
阅读量:7082 次
发布时间:2019-06-28

本文共 46472 字,大约阅读时间需要 154 分钟。

                        GO语言的进阶之路-协程和Channel

                                                作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

   看过我之前几篇博客小伙伴可能对Golang语言的语法上了解的差不多了,但是,如果想要你的代码和性能更高,那还得学点晋升的本来,这个时候我们就需要引入Golang的协成这个概念了,其实,你可能早就听说了Golang的优势就是处理大并发,我们可以用它来做日志收集系统,也可以用它做业务上的“秒杀系统”,当然我们还可以用它来做“监控系统”。好了,下面跟我一起来体会一下Golang的五味杂陈吧。

 

一.什么是协程;

  再说协成之前,我们需要了解两个概念,即用户态和内核态。

1.什么是用户态;

  官方解释:用户态(user mode)在计算机结构指两项类似的概念。在CPU的设计中,用户态指非特权状态。在此状态下,执行的代码被硬件限定,不能进行某些操作,比如写入其他的存储空间,以防止给带来安全隐患。在的设计中,用户态也类似,指非特权的执行状态。禁止此状态下的代码进行潜在危险的操作,比如写入系统配置文件、杀掉其他用户的、重启系统等。

   应用程序在用户态下运行,仅仅只能执行cpu整个指令集的一个子集,该子集中不包含操作硬件功能的部分,因此,一般情况下,在用户态中有关I/O和内存保护(操作系统占用的内存是受保护的,不能被别的程序占用)。

  如果感兴趣的朋友可以参考:

 

2.什么是内核态;

  内核态也叫和核心态。

  官方解释:在处理器的存储保护中,主要有两种权限状态,一种是核心态(),也被称为特权态;一种是用户态()。核心态是所运行的模式,运行在该模式的代码,可以无限制地对系统存储、进行访问。

  操作系统在内核态运行情况下可以访问硬件上所有的内容。

  如果感兴趣的朋友可以参考:

 

3.什么是协成;

  官方解释:一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

  协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。协成工作在用户态,它类似于现场的运行方式可以并行处理任务。

   如果感兴趣的朋友可以参考:

 

二.创建一个协程;

   在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行,下面我们一起来看段代码的执行结果:

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11 "time"12 "fmt"13 )14 15 func sayhi(s string)  {16     for i := 0; i < 5; i++ {17         time.Sleep(100*time.Millisecond)  //表示每次循环后都要休息100毫秒。18         fmt.Println(s)19     }20 }21 22 func main() {23     go sayhi("尹正杰")  //在函数执行前加个go,表示单独起了一个协程,表示和当前主协程(main)并驾齐驱运行代码。24     sayhi("Golang")25 }26 27 28 #以上代码执行结果如下:29 Golang30 尹正杰31 尹正杰32 Golang33 Golang34 尹正杰35 尹正杰36 Golang37 Golang38 尹正杰

   相信大家已经看到了一些端倪,我们先定义了一个“sayhi”函数,这个函数的功能就是将传入的字符串打印5遍,然后我们在主程序上调用的时候,发现在函数面前加"go"关键字时,就会打印输出,但是奇怪的是没有按照顺序打,它没有先打印五遍“尹正杰”然后在打印5遍“Golang”,而是函数执行结果是不规律的打印了两个字符串。这就是并发的效果,两个函数同事运行效果。这也是我们的并发之路的初体验。哈哈~

 

 

三.协程的局限性;

  发现让两端代码同事运行貌似听起来很高大上的样子,但是有个局限性,就是当主进程结束后,协成也会跟着结束,其实这个很好理解,我们通过一段代码就知道了:

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13 )14 15 func main() {16     s := []int{2,7,1,6,4,3,11,15,17,5,8,9,12}17     for _,n := range s{18         go func(n int) { //定义一个匿名函数,并对该函数开启协程,每次循环都会开启一个协成,也就是说它开启了13个协程。19             time.Sleep(time.Duration(n) * time.Second) /*表示每循环一次就需要睡1s,睡的总时间是由n来控制的,20             总长度是由s切片数组中最大的一个数字决定,也就是说这个协成最少需要17秒才会结束哟*/21             fmt.Println(n)22         }(n)  //由于这个函数是匿名函数,所以调用方式就直接:(n)调用,不用输入函数名。23     }24     time.Sleep(12*time.Second) //主进程要执行的时间是12秒25 }26 27 28 29 #以上代码执行结果如下:30 131 232 333 434 535 636 737 838 939 1140 12

  是不是很神奇,直接就进行排序了,但是15和17没有在终端输出,为什么呢?这就是我所说的主进程结束,那么整个程序也终将结束,因为主程序的时间只有12s,如果你想让所有的数据都排序出来,就得把数字换成一个大于或等于17的,这样才会对所有的数字进行排序。因为协成13个协程都要执行完毕的话需要17s才好使。

 

四.锁;

1.为什么要引入锁;

  在线上生活中,我们为了避免多个用户操作同一个文件,就会定义一个锁,为什么我们需要一个锁呢?我们可以看以下的案例:

1 package main 2  3 import ( 4     "time" 5     "fmt" 6 ) 7  8 type Accout struct { 9     money int10 }11 12 func (a *Accout) Do_Prepare() {13     time.Sleep(time.Second)14 }15 16 func (a *Accout) Get_Gongzi(n int) {17     a.money += n18 }19 20 func (a *Accout) Give_Wife(n int) {21     if a.money > n {22         a.Do_Prepare()23         a.money -= n24     }25 }26 27 func (a *Accout) Buy(n int) {28     if a.money >n {29         a.Do_Prepare()30         a.money -= n31     }32 }33 34 func (a *Accout) Left()int {35     return a.money36 }37 38 func main() {39     var   account Accout40     account.Get_Gongzi(10000)41     go account.Give_Wife(6000)42     go account.Buy(5000)43     time.Sleep(2 * time.Second) //不能让主程序结束掉,因为主进程一结束go的协程也就跟着结束啦。44     fmt.Println(account.Left())45 }46 47 48 49 #以上代码执行结果如下:50 -1000

 

  估计大家都看出来端倪了,在让两个协程并发跑起来,发现你得到的10000块钱都让给花出去了,是不是老尴尬了,写的判断语句也是没有生效的,最终10000块钱的工资,它竟然花了11000,如果银行这么干早就倒闭了,哈哈~那么问题来了,如果解决这一种现象呢?目前有四种常见的处理方案即:互斥锁,读写锁和channel锁以及waitgroup等等。

 

2.互斥锁;

  互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法——Lock和Unlock。顾名思义,前者被用于锁定当前的互斥量,而后者则被用来对当前的互斥量进行解锁。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13     "sync"14 )15 16 17 type Accout struct {18     flag sync.Mutex19     money int20 }21 22 func (a *Accout) Do_Prepare() {23     time.Sleep(time.Second)24 }25 26 func (a *Accout) Get_Salary(n int) { //定义发的工资27     a.money += n28 }29 30 func (a *Accout) Give_Wife(n int) {  //上交给妻子的工资31     a.flag.Lock()32     defer a.flag.Unlock()33     if a.money > n {34         a.Do_Prepare()35         a.money -= n36     }else {37         fmt.Println("您的余额已不足!请及时充值~")38     }39 }40 41 func (a *Accout) Buy(n int) {  //自己买的工资42     a.flag.Lock()43     defer a.flag.Unlock()44     if a.money >n {45         a.Do_Prepare()46         a.money -= n47     }else {48         fmt.Println("您的余额已不足!请及时充值~")49     }50 }51 52 func (a *Accout) Left()int {53     return a.money54 }55 56 func main() {57     var   account Accout58     account.Get_Salary(10000)59     go account.Give_Wife(6000)60     go account.Buy(5000)61     time.Sleep(2 * time.Second) //不能让主程序结束掉,因为主进程一结束go的协程也就跟着结束啦。62     fmt.Printf("您的剩余工资是\033[31;1m%d\033[0m",account.Left())63 64 }65 66 67 68 #以上代码解析如下:69 您的余额已不足!请及时充值~70 您的剩余工资是4000

 

 

3.读写锁;

  在Go语言中,读写锁由结构体类型sync.RWMutex代表。与互斥锁类似,sync.RWMutex类型的零值就已经是立即可用的读写锁了。

  读写锁即是针对于读写操作的互斥锁。它与普通的互斥锁最大的不同就是,它可以分别针对读操作和写操作进行锁定和解锁操作。读写锁控制下的多个写操作之间都是互斥的,并且写操作与读操作之间也都是互斥的。但是,多个读操作之间却不存在互斥关系。

 

A.多个读操作之间却不存在互斥关系;

  我们会发现读取操作没有存在互斥的关系。5个进程同事进行读取,最终都顺利并行跑完了。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "sync"12     "time"13 )14 15 var m *sync.RWMutex  //定义m的类型为读写锁。16 17 func main() {18     m = new(sync.RWMutex)19     go read("第一次读取:")  //开启5个协程进行读取操作。如果不能并发跑最少需要5秒钟时间。20     go read("第二次读取:")21     go read("第三次读取:")22     go read("第四次读取:")23     go read("第五次读取:")24     time.Sleep(2* time.Second)  //主进程只有2s,意味着这个程序最多能运行2s的时间。25 }26 27 func read(i string) {28     println(i, "read start")29     m.RLock()  //读锁定30     println(i, "资料正在读取中....")31     time.Sleep(1 * time.Second)  //改函数执行完毕最少需要睡1秒。32     m.RUnlock() //读解锁33     println(i, "read end")34 }35 36 37 38 #以上代码执行结果如下:39 第二次读取: read start40 第二次读取: 资料正在读取中....41 第一次读取: read start42 第一次读取: 资料正在读取中....43 第三次读取: read start44 第三次读取: 资料正在读取中....45 第四次读取: read start46 第四次读取: 资料正在读取中....47 第五次读取: read start48 第五次读取: 资料正在读取中....49 第一次读取: read end50 第三次读取: read end51 第二次读取: read end52 第四次读取: read end53 第五次读取: read end

 

 

B.读写锁控制下的多个写操作之间都是互斥的,并且写操作与读操作之间也都是互斥的;

  我们发现,读的时候不能写,写的时候不能读,我们将主程序设置的时间是10秒钟,一次读两次写花费的总时间是12秒钟,因此肯定有一个是无法完成度或是写的部分。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "sync"12     "time"13 )14 15 var m *sync.RWMutex16 17 func read(i string) {18     println(i, "read start")19     m.RLock()20     println(i, "资料正在读取中.....")21     time.Sleep(4 * time.Second)22     m.RUnlock()23     println(i, "read end")24 }25 26 func write(i string) {27     println(i, "write start")28     m.Lock() //写操作锁定29     println(i, "数据正在写入硬盘中.....")30     time.Sleep(4 * time.Second)31     m.Unlock()  //写操作解锁32     println(i, "write end")33 }34 35 func main(){36     m = new(sync.RWMutex)37     go write("第一次写入") //写的时候啥都不能干,即其他协程无法写入,也无法读取。38     go read("第一次读取")39     go write("第二次写入")40     time.Sleep(10 * time.Second) //主进程只给出10秒的时间,但是整个进程跑完最少需要12秒的时间。41 }42 43 44 45 46 #以上代码直接结果如下:47 第一次写入 write start48 第一次写入 数据正在写入硬盘中.....49 第一次读取 read start50 第二次写入 write start51 第一次写入 write end52 第一次读取 资料正在读取中.....53 第一次读取 read end54 第二次写入 数据正在写入硬盘中.....

 

4.channel锁;

  channel锁的工作原理就是让2个协成互相通信,一会我们会详细介绍。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13     "sync"14 )15 16 17 type Accout struct {18     flag sync.Mutex19     money int20 }21 22 func (a *Accout) Do_Prepare() {23     time.Sleep(time.Second)24 }25 26 func (a *Accout) Get_Gongzi(n int) {27     a.money += n28 }29 30 func (a *Accout) Give_Wife(n int) {31     a.flag.Lock()32     defer a.flag.Unlock()33     if a.money > n {34         a.Do_Prepare()35         a.money -= n36     }else {37         fmt.Println("您的余额已不足,请及时充值!")38     }39 }40 41 func (a *Accout) Buy(n int) {42     a.flag.Lock()43     defer a.flag.Unlock()44     if a.money >n {45         a.Do_Prepare()46         a.money -= n47     }else {48         fmt.Println("您的余额已不足,请及时充值!")49     }50 }51 52 func (a *Accout) Left()int {53     return a.money54 }55 56 func main() {57     var   account Accout58     account.Get_Gongzi(10000)59 60     var work_info  chan string61     work_info = make(chan string ,2)  //定义一个channel62 63     go func() {64         account.Give_Wife(6000)65         work_info <- "I done"  //如果该进程执行完毕就发送一条数据给channel,下面的那个也一样66     }()67 68     go func() {69         account.Buy(50000)70         work_info <- "I  have done too!"71     }()72 73     cnt := 074     for i := range work_info {75         fmt.Println(i)76         cnt++ //每次循环都叠加1,当2个协程都工作完就让主程序结束。77         if cnt >= 2 {78             break79         }80 81     }82     defer close(work_info)83     fmt.Printf("您的剩余工资是\033[31;1m%d\033[0m",account.Left())84 }85 86 87 88 89 #以上代码直接结果如下:90 您的余额已不足,请及时充值!91 I  have done too!92 I done93 您的剩余工资是4000

 

  其实channel还有一个好处就是解决超时问题,就是如果在规定的时间没有完成进程的内容,我们就返回给用户超时的状态。以下是示例代码:

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13     "sync"14 )15 16 17 type Accout struct {18     flag sync.Mutex19     money int20 }21 22 func (a *Accout) Do_Prepare() {23     time.Sleep(time.Second)24 }25 26 func (a *Accout) Get_Gongzi(n int) {27     a.money += n28 }29 30 func (a *Accout) Give_Wife(n int) {31     a.flag.Lock()32     defer a.flag.Unlock()33     if a.money > n {34         a.Do_Prepare()35         a.money -= n36     }else {37         fmt.Println("您的余额已不足,请及时充值!")38     }39 }40 41 func (a *Accout) Buy(n int) {42     a.flag.Lock()43     defer a.flag.Unlock()44     if a.money >n {45         a.Do_Prepare()46         a.money -= n47     }else {48         fmt.Println("您的余额已不足,请及时充值!")49     }50 }51 52 func (a *Accout) Left()int {53     return a.money54 }55 56 func main() {57     var   account Accout58     account.Get_Gongzi(10000)59 60     var work_info  chan string61     work_info = make(chan string ,2)  //定义一个channel62 63     go func() {64         account.Give_Wife(6000)65         work_info <- "I done"  //如果该进程执行完毕就发送一条数据给channel,下面的那个也一样66     }()67 68     go func() {69         account.Buy(50000)70         work_info <- "I  have done too!"71     }()72 73     cnt := 074     for i := range work_info {75         fmt.Println(i)76         cnt++ //每次循环都叠加1,当2个协程都工作完就让主程序结束。77         if cnt >= 2 {78             fmt.Println("操作超时")79             return80         }81 82     }83     defer close(work_info)84     fmt.Printf("您的剩余工资是\033[31;1m%d\033[0m",account.Left())85 }86 87 88 89 #以上代码执行结果如下:90 您的余额已不足,请及时充值!91 I  have done too!92 I done93 操作超时

 

5.waitgroup进行同步;

  WaitGroup在go语言中,用于线程同步,它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13     "sync"14 )15 16 type Accout struct {17     flag sync.Mutex18     money int19 }20 21 func (a *Accout) Do_Prepare() {22     time.Sleep(time.Second)23 }24 25 func (a *Accout) Get_Gongzi(n int) {26     a.money += n27 }28 29 func (a *Accout) Give_Wife(n int) {30     a.flag.Lock()31     defer a.flag.Unlock()32     if a.money > n {33         a.Do_Prepare()34         a.money -= n35     }else {36         fmt.Println("您的余额已不足,请及时充值!")37     }38 }39 40 func (a *Accout) Buy(n int) {41     a.flag.Lock()42     defer a.flag.Unlock()43     if a.money >n {44         a.Do_Prepare()45         a.money -= n46     }else {47         fmt.Println("您的余额已不足,请及时充值!")48     }49 }50 51 func (a *Accout) Left()int {52     return a.money53 }54 55 func main() {56     var   account Accout57     account.Get_Gongzi(10000)58     wg := new(sync.WaitGroup) //定义一个WaitGroup,就是用来等待都59     wg.Add(2) //有几个进程需要等待就写几个,写多或写少都会报错哟~60     go func() {61         account.Give_Wife(6000)62         wg.Done() //该进程结束就发送结束标志。63     }()64     go func() {65         account.Buy(5000)66         wg.Done()67     }()68     wg.Wait()  //等待所有协程结束后在执行以下都代码。69     fmt.Printf("您的剩余工资是\033[31;1m%d\033[0m",account.Left())70 }71 72 73 74 #以上代码执行结果如下:75 您的余额已不足,请及时充值!76 您的剩余工资是5000 

 

五.协程池;

  协程池可以控制并行度,复用协程。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "sync"12     "net/http"13     "log"14 )15 16 func work(ch chan string, wg *sync.WaitGroup) { //ch是用来接受数据,wg是用来同步协程的.17     for u := range ch{18         resp,err := http.Get(u) //将channel中的数据读取出来19         if err != nil {20             log.Print(err)21             return22         }23         log.Printf("访问的网站是:%s,网页的大小是:%v",u,resp.ContentLength)24         resp.Body.Close() //25     }26     wg.Done()27 }28 29 func main() {30     wg := new(sync.WaitGroup) //定义WaitGroup为了进行协程同步。31     wg.Add(5) //开启5个协程池。32     taskch := make(chan string) //创建一个channel33     for i := 0; i < 5; i++ {34         go work(taskch,wg)35     }36     urls := []string{
"http://www.baidu.com","http://www.zhihu.com","http://www.cnblogs.com/yinzhengjie/p/7201980.html"} //定义需要访问的网站。37 for _,url := range urls{38 taskch <- url //往channel发送数据。39 }40 close(taskch)41 wg.Wait() //等待程序结束42 }43 44 45 46 #以上代码执行结果如下:47 2017/07/24 15:23:02 访问的网站是:http://www.baidu.com,网页的大小是:-148 2017/07/24 15:23:03 访问的网站是:http://www.cnblogs.com/yinzhengjie/p/7201980.html,网页的大小是:-149 2017/07/24 15:23:03 访问的网站是:http://www.zhihu.com,网页的大小是:-1

 

六.channel的引入;

  Go 语言中的 channel 是实现  goroutine 间无锁通信的关键机制,他使得写多线程并发程序变得简单、灵活、触手可得。channel是Go语言在语言级别提供的goroutine间的通信方式,我们可以使用channel在多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。

 

1.实现channel的Receive与Sender;

  初学者可以理解成:channel实际上就是接受和发送数据。channel是类型相关的,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。有了这些就够了,我们一起来看段代码来感受下channel吧。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import "fmt"11 12 13 14 func sum(s []int, c chan int) {  //该函数是对切片数组求和,需要传入一个切片数组和一个channel。15     sum := 016     for _,v := range s {17         sum += v //表示对切片数组进行求和。18     }19     c <- sum   //将sum的数据送给名称为c的channel,也就是说将数组中的数字的值传给channel。给channle赋值的时候,其应该在左边。20 }21 22 func main() {23     Slice_array := []int{1,2,3,4,-9,4,2,0}24     channel_name := make(chan int)  //用chan定义一个channel对象名称为channel_name,其类型是int。25     go sum(Slice_array[:len(Slice_array)/2],channel_name)  //在函数上开启协成,注意格式。是在函数前面加个go参数即可。26     go sum(Slice_array[len(Slice_array)/2:],channel_name)27     x,y := <-channel_name, <-channel_name  //将channel_name中的数据接收并复制给x和y,当然你也可以定义两个变量名不同的channle,要注意到是取channel的值时,其应该在右边,28     fmt.Println(x,y,x+y)29 }30 31 32 33 #以上代码执行结果如下:34 -3 10 7

 

2.channel的缓冲大小;

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import "fmt"11 12 13 14 func main() {15     ch := make(chan int,5)  /*定义缓冲大小容量为5。下面给channel发送了3个值,如果接受当值超过预定义的5时,就会16      报错哟!如果你不写的化就会报错:"fatal error: all goroutines are asleep - deadlock!"*/17     ch <- 10018     ch <- 20019     ch <- 30020     fmt.Println(<-ch)  //第一次取值21     fmt.Println(<-ch)  //第二次取值22     fmt.Println(<-ch)  //第三次取值23 }24 25 26 27 #以上代码执行结果如下:28 10029 20030 300

 

3.channel的应用;

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import "fmt"11 12 func fibonacci(n int,c chan int) { //定义斐波拉契数列函数。13     x,y := 0,114     for i := 0; i < n; i++ { 15         c <- x16         x,y = y,x+y17         if x > 100 {  //表示当循环到两个数字之和大于100时就终止循环。18             break19         }20     }21     close(c) //关闭channel,如果不关闭就会被锁,出现报错:"deadlock!"22 }23 24 func main() {25     channel_name := make(chan int,15) //定义channel缓冲大小时为15,表示最多存取15个元素。26     go fibonacci(cap(channel_name),channel_name)27     for i := range channel_name{  //遍历channel_name28         fmt.Println(i)29     }30 }31 32 33 34 #以上代码执行结果如下:35 036 137 138 239 340 541 842 1343 2144 3445 5546 89

 

 4.Channeld的select语法;

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import "fmt"11 12 func fibonacci(channel_name,quit chan int)  { //定义两个channle对象channel_name和quit。13     x,y := 0,114     for{15         select {16         case channel_name <- x:  //用channel_name接受数据。17             x,y = y,x+y18 19         case <-quit:  //表示当接收到quit的channel时,就执行以下代码。其实就是实现关闭channel的功能。20             fmt.Println("EXIT")21             return //函数一退出协程也就跟着退出了22         }23     }24 }25 26 func main() {27     channel_name := make(chan int)28     quit := make(chan int)29     go func() {30         for i := 0; i < 11; i++ {31             fmt.Println(<-channel_name)  //"<-channel_name"表示读取channel_name中的参数。32         }33         quit<- 100 /*当for循环结束后,我们随便给quit的channel传一个值就可以实现退出函数的功能。我们之前需要34         用close(c)来退出发信号的功能,主动权在"fibonacci",而我们现在我们用quit来主动退出协程。*/35     }() //这里加个括号是在调用当前函数。36     fibonacci(channel_name,quit)  //将channel_name和quit传递给fibonacci函数37 }38 39 40 41 #以上代码珍惜结果如下:42 043 144 145 246 347 548 849 1350 2151 3452 5553 EXIT

 

 5.default语法;

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13 )14 15 func main() {16     tick := time.Tick(1000*time.Millisecond) //也可以这样写:“tick := time.NewTicker(1000*time.Millisecond).C”其中这个点C就是一个channel。17     boom := time.After(5000*time.Millisecond)18     for  {19         select {20         case <-tick:21             fmt.Println("滴答。。。")22         case <-boom:23             fmt.Println("砰~")24             return25         default:26             fmt.Println("吃一口凉皮")27             time.Sleep(500*time.Millisecond)28         }29     }30 }31 32 33 34 #以上代码执行结果如下:35 吃一口凉皮36 吃一口凉皮37 滴答。。。38 吃一口凉皮39 吃一口凉皮40 滴答。。。41 吃一口凉皮42 吃一口凉皮43 滴答。。。44 吃一口凉皮45 吃一口凉皮46 滴答。。。47 吃一口凉皮48 吃一口凉皮49 砰~

 

 6.channel扩展;

A.time.NewTicker用法展示:

  当我们在写一个监控系统的时候,需要实施监控,但是定义巡检时间的话,需要我们自定义巡检周期性。这个时候我们就可以用到一个叫时间设置器的函数,即:time.NewTicker。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13 )14 15 func main() {16     timer := time.NewTicker(3 * time.Second)  //设置定时器,定义timer的基本单位是三秒。17     cnt := 018     for _ = range timer.C{  //其中timer.C就是一个channel19         cnt++20         if cnt > 3 {21             timer.Stop()  //调用方式22             return //这里是手动停下来,一般是不会让他停下来,比如在监控的时候,但是有结束条件的时候用return的话是不会报错的23         }24         fmt.Println("yinzhengjie")25     }26 }27 28 29 30 #以上代码执行结果如下:31 32 yinzhengjie33 yinzhengjie34 yinzhengjie

 

B.time.After用法展示:

  要注意的是,“time.NewTicker”需要调用"Stop"方法才会被执行,而“time.After”调用的姿势也不一样,我们一起来体验一下吧。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "time"12     "fmt"13 )14 15 func main() {16     c := time.After(time.Second*3)  //表示让其睡眠3秒钟。17     <-c   //调用方式18     fmt.Println("主进程执行完毕")19 }20 21 22 23 #以上代码执行结果如下:24 主进程执行完毕

 

C.channel的深入浅出;

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "net/http"12     "log"13     "fmt"14     "time"15 )16 17 /*18 1.channel就好像一个传送带,可以源源不断的往里面放数据,只要不close就可以永远发送数据。19 2.如果channel里面没有数据,接收方会阻塞。20 3.如果没有人正在等待channel的数据,发送方会阻塞。21 4.从一个close的channel取数据永远不会阻塞,同时获取的是默认值22 */23 24 25 func Print_url(url string) {26     res,err := http.Get(url)27     if err != nil {28         log.Fatal("11111",err)29     }30     defer res.Body.Close()31     fmt.Printf("网页的状态返回码是:%v\n",res.Status)32 33 }34 35 func work(resave chan string) {36 37     for  {38         url,ok := <- resave   //如果channel里面没有数据,接收方会阻塞。用url,ok两个参数来接受channel,可以从channel会返回两个参数。url表示接受的数据。ok接受的信息是channel是否结束。39         //fmt.Println(url,ok)40         if !ok { //判断channel是否关闭。41             break42         }43         Print_url(url)44     }45 46     //for i := range resave { //range语法一值在接受数据,而channel可以源源不断的往里面添加数据。从一个close的channel取数据永远不会阻塞,同时获取的是默认值。47     //    Print_url(i)48     //}49 }50 51 func main() {52     make_string := make(chan string)53     for i := 0;i<3 ;i++  {54         go work(make_string)55     }56     urls := []string{
"http://www.baidu.com","http://www.qq.com","http://www.weixin.com"}57 for _,i := range urls {58 make_string <- i //如果没有人正在等待channel的数据,发送方会阻塞。59 }60 close(make_string) //channel就好像一个传送带,可以源源不断的往里面放数据,只要不close就可以永远发送数据。61 time.Sleep(5 * time.Second)62 }63 64 65 66 67 #以上代码输出结果如下:68 网页的状态返回码是:200 OK69 网页的状态返回码是:200 OK70 网页的状态返回码是:200 OK

 

D.waitgroup+channel配合使用;

  我们可以对C进行优化,在实际生产环境中,我们最好不要用"time.sleep"这个方法来实现主程序等待子程序执行完毕,因为会出现异响不到的bug哟~推荐是用waitgrout来取代这个sleep时间。

1 package main 2  3 import ( 4     "net/http" 5     "log" 6     "fmt" 7     //"time" 8     "sync" 9 )10 11 /*12 1.channel就好像一个传送带,可以源源不断的往里面放数据,只要不close就可以永远发送数据。13 2.如果channel里面没有数据,接收方会阻塞。14 3.如果没有人正在等待channel的数据,发送方会阻塞。15 4.从一个close的channel取数据永远不会阻塞,同时获取的是默认值16 */17 18 19 func Print_url(url string) {20     res,err := http.Get(url)21     if err != nil {22         log.Fatal("11111",err)23     }24     defer res.Body.Close()25     fmt.Printf("网页的状态返回码是:%v\n",res.Status)26 27 }28 29 func work(resave chan string,wg *sync.WaitGroup) {30 31     for  {32         url,ok := <- resave   //如果channel里面没有数据,接收方会阻塞。用url,ok两个参数来接受channel,可以从channel会返回两个参数。url表示接受的数据。ok接受的信息是channel是否结束。33         //fmt.Println(url,ok)34         if !ok { //判断channel是否关闭。35             break36         }37         Print_url(url)38     }39 40     //for i := range resave { //range语法一值在接受数据,而channel可以源源不断的往里面添加数据。从一个close的channel取数据永远不会阻塞,同时获取的是默认值。41     //    Print_url(i)42     //}43     wg.Done()44 }45 46 func main() {47     wg := new(sync.WaitGroup)48     //var num = 349     //make_string := make(chan string)50     //for i := 0;i

 

七.爬虫进阶之路;

  在我们想要写出来一个爬虫出来,我们首先得了解几个模块,知道它的用法,这样我们才能写出一个真正的爬虫,接下来跟我一起看看常用的几个模块吧。

1."net/http"模块用法展示;

  "net/http"模块可用于爬去url的内容。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "net/http"12     "log"13     "io"14     "os"15 )16 17 func main() {18     url := "http://www.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。19     resp,err := http.Get(url) //获取url的内容。20     if err != nil {21         log.Fatal(err)22     }23     defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露。24     if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。25         log.Fatal(resp.Status)26     }27     io.Copy(os.Stdout,resp.Body) //将正常的结果输出到屏幕上,并不会占用内存。28 }
1 [root@yinzhengjie tmp]# more http.go   2 package main  3   4 import (  5         "net/http"  6         "log"  7         "io"  8         "os"  9 ) 10  11 func main() { 12         url := "http://www.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。 13         resp,err := http.Get(url) 14         if err != nil { 15                 log.Fatal(err) 16         } 17         defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露。 18         if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。 19                 log.Fatal(resp.Status) 20         } 21         io.Copy(os.Stdout,resp.Body) //将结果输出到屏幕上,并不会占用内存。 22 } 23  24 [root@yinzhengjie tmp]#  25 [root@yinzhengjie tmp]#  26 [root@yinzhengjie tmp]# go run http.go  27  28  29  30 
31
32
33
34 -УУtitle>
37
38
39
40
41 42 43 44 45 46 47
48 49 50
51
52
53 54
55
56
57
65
66
67
70 71
72
73
74
83
87
88
89
90
92
104
105
106
108
109
123
124
125
126
127
ӯ
128
129
130
131
Ůdiv>
132
133
  • ><span>span><em></em></a></li><li><a href=><span>span><em></em></a></li><li><a href=><span>span><em></em></a></li><li><a href=><span>span><em></em></a></li><li><a href=><span>span><em></em></a></li><li><a href=><span>span><em></em></a></li>134 <div class=
135 136
137
138
139
140
Уѯ
149
/dt>
Ůa>
150
Уa href="http://www.xiaohuar.com/p-1-3.html" target="_blank">MM
΢С΢
a>
a>
a>
a>
151
152
153
154 155
156 157 158
159
167
168
169
254
255
256

У href="http://www.xiaohuar.com/">ȫѧУƬɢͬУͼƬ href="http://www.xiaohuar.com/">УƬԭУƬr/>Уa> © 2009-2015 All Rights Reserved www.xiaohuar.com class="__cf_email__" href="/cdn-cgi/l/email-protection" data-cfemail="c0f2f1f1f2f4f9f2f3f680b1b1eea3afad">[email protected] P006982

257
258
259
 
260 261
262
263
264
265 266
267
268
269
270
271
272 284 288 289 290 [root@yinzhengjie tmp]#
以上代码执行结果在这里

 

2.“net/url”模块用法展示;

  “net/url”模块中我们会用到一个解析方法,它可以解析url使用的协议,主机名,当前路径,请求信息,用户信息,当前锚点等等。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "os"12     "net/url"13     "log"14     "fmt"15 )16 17 func main() {18     s := os.Args[1]19     u,err := url.Parse(s)20     if err != nil {21         log.Fatal(err)22     }23     fmt.Println("使用的协议是:",u.Scheme)24     fmt.Println("主机名称是:",u.Host)25     fmt.Println("当前路径是:",u.Path)26     fmt.Println("请求信息是:",u.RawQuery)27     fmt.Println("用户信息是:",u.User)28     fmt.Println("当前的锚点是:",u.Fragment)29 }
1 [root@yinzhengjie tmp]# more url.go  2 /* 3 #!/usr/bin/env gorun 4 @author :yinzhengjie 5 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 6 EMAIL:y1053419035@qq.com 7 */ 8  9 package main10 11 import (12         "os"13         "net/url"14         "log"15         "fmt"16 )17 18 func main() {19         s := os.Args[1]20         u,err := url.Parse(s)21         if err != nil {22                 log.Fatal(err)23         }24         fmt.Println("使用的协议是:",u.Scheme)25         fmt.Println("主机名称是:",u.Host)26         fmt.Println("当前路径是:",u.Path)27         fmt.Println("请求信息是:",u.RawQuery)28         fmt.Println("用户信息是:",u.User)29         fmt.Println("当前的锚点是:",u.Fragment)30 }31 [root@yinzhengjie tmp]# 32 [root@yinzhengjie tmp]# 33 [root@yinzhengjie tmp]# go run url.go http://www.cnblogs.com/yinzhengjie/p/7202796.html34 使用的协议是: http35 主机名称是: www.cnblogs.com36 当前路径是: /yinzhengjie/p/7202796.html37 请求信息是: 38 用户信息是: 
39 当前的锚点是: 40 [root@yinzhengjie tmp]#
以上代码用法展示戳这里

 

3.goquery模块用法展示;

  做过 Web 开发的,应该都用过或听过 jQuery,它提供了方便的操作 DOM 的 API。使用 Go 语言做服务器端开发,有时候需要解析 HTML 文件,比如抓取网站内容、写一个爬虫等。这时候如果有一个类似 jQuery 的库可以使用,操作 DOM 会很方便,而且,上手也会很快。 这个库就实现了类似 jQuery 的功能,让你能方便的使用 Go 语言操作 HTML 文档。

  首先,要强调一点,要执行以下代码需要安装一个goquery模块,该模块用来爬去网站的内容的简直就是一个神器啊。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "net/http"12     "log"13     "github.com/PuerkitoBio/goquery"14     "fmt"15 )16 17 18 /*19     用goquery可以快速获取url的标签。20 */21 22 func main() {23     url := "http://www.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。24     resp,err := http.Get(url)  获取url的内容,包括请求头信息都在里面放着的。25 26     if err != nil {27         log.Fatal(err)28     }29     defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露。30     if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误并终止程序。31         log.Fatal(resp.Status)32     }33     //io.Copy(os.Stdout,resp.Body) //将结果输出到屏幕上,并不会占用内存。34     doc,err := goquery.NewDocumentFromResponse(resp)  //对resp进行过滤,最终会拿到首页路径url35     if err != nil {36         log.Fatal(err)37     }38     doc.Find("img").Each(func(i int, s  *goquery.Selection) { /*表示找到"img"的标签然后用Each方法遍历每一个图片路径。*/39         link,ok := s.Attr("src") //找到“img”标签后会将其的“src”的路径找到,最终也就找到了图片的路径。40         if ok {41             fmt.Println(link)  //如果存在“src”路径就打印42         }else {43             fmt.Println("抱歉,没有发现该路径。")44         }45     })46 }
1 [root@yinzhengjie tmp]# more goquery.go   2 /*  3 #!/usr/bin/env gorun  4 @author :yinzhengjie  5 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/  6 EMAIL:y1053419035@qq.com  7 */  8   9 package main 10  11 import ( 12         "net/http" 13         "log" 14         "github.com/PuerkitoBio/goquery" 15         "fmt" 16 ) 17  18  19 /* 20         用goquery可以快速获取url的标签。 21 */ 22  23 func main() { 24         url := "http://www.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。 25         resp,err := http.Get(url)  获取url的内容,包括请求头信息都在里面放着的。 26  27         if err != nil { 28                 log.Fatal(err) 29         } 30         defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露。 31         if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误并终止程序。 32                 log.Fatal(resp.Status) 33         } 34         //io.Copy(os.Stdout,resp.Body) //将结果输出到屏幕上,并不会占用内存。 35         doc,err := goquery.NewDocumentFromResponse(resp)  //对resp进行过滤,最终会拿到首页路径url 36         if err != nil { 37                 log.Fatal(err) 38         } 39         doc.Find("img").Each(func(i int, s  *goquery.Selection) { /*表示找到"img"的标签然后用Each方法遍历每一个图片路径。*/ 40                 link,ok := s.Attr("src") //找到“img”标签后会将其的“src”的路径找到,最终也就找到了图片的路径。 41                 if ok { 42                         fmt.Println(link)  //如果存在“src”路径就打印 43                 }else { 44                         fmt.Println("抱歉,没有发现该路径。") 45                 } 46         }) 47 } 48  49 [root@yinzhengjie tmp]#  50 [root@yinzhengjie tmp]#  51 [root@yinzhengjie tmp]# go run goquery.go  52 http://www.xiaohuar.com/skin/meizi/images/grey.gif 53 http://www.xiaohuar.com/skin/meizi/images/grey.gif 54 http://www.xiaohuar.com/skin/meizi/images/grey.gif 55 http://www.xiaohuar.com/skin/meizi/images/grey.gif 56 http://www.xiaohuar.com/skin/meizi/images/grey.gif 57 http://www.xiaohuar.com/skin/meizi/images/grey.gif 58 http://www.xiaohuar.com/skin/meizi/images/grey.gif 59 http://www.xiaohuar.com/skin/meizi/images/grey.gif 60 http://www.xiaohuar.com/skin/meizi/images/grey.gif 61 http://www.xiaohuar.com/skin/meizi/images/grey.gif 62 http://www.xiaohuar.com/skin/meizi/images/grey.gif 63 http://www.xiaohuar.com/skin/meizi/images/grey.gif 64 http://www.xiaohuar.com/skin/meizi/images/grey.gif 65 http://www.xiaohuar.com/skin/meizi/images/grey.gif 66 http://www.xiaohuar.com/skin/meizi/images/grey.gif 67 http://www.xiaohuar.com/skin/meizi/images/grey.gif 68 http://www.xiaohuar.com/skin/meizi/images/grey.gif 69 http://www.xiaohuar.com/skin/meizi/images/grey.gif 70 http://www.xiaohuar.com/skin/meizi/images/grey.gif 71 http://www.xiaohuar.com/skin/meizi/images/grey.gif 72 http://www.xiaohuar.com/skin/meizi/images/grey.gif 73 http://www.xiaohuar.com/skin/meizi/images/grey.gif 74 http://www.xiaohuar.com/skin/meizi/images/grey.gif 75 http://www.xiaohuar.com/skin/meizi/images/grey.gif 76 http://www.xiaohuar.com/skin/meizi/images/grey.gif 77 http://www.xiaohuar.com/skin/meizi/images/grey.gif 78 http://www.xiaohuar.com/skin/meizi/images/grey.gif 79 http://www.xiaohuar.com/skin/meizi/images/grey.gif 80 http://www.xiaohuar.com/skin/meizi/images/grey.gif 81 http://www.xiaohuar.com/skin/meizi/images/grey.gif 82 http://www.xiaohuar.com/skin/meizi/images/grey.gif 83 http://www.xiaohuar.com/skin/meizi/images/grey.gif 84 http://www.xiaohuar.com/skin/meizi/images/grey.gif 85 http://www.xiaohuar.com/skin/meizi/images/grey.gif 86 http://www.xiaohuar.com/skin/meizi/images/grey.gif 87 http://www.xiaohuar.com/skin/meizi/images/grey.gif 88 http://www.xiaohuar.com/skin/meizi/images/grey.gif 89 http://www.xiaohuar.com/skin/meizi/images/grey.gif 90 http://www.xiaohuar.com/skin/meizi/images/grey.gif 91 http://www.xiaohuar.com/skin/meizi/images/grey.gif 92 http://www.xiaohuar.com/skin/meizi/images/grey.gif 93 http://www.xiaohuar.com/skin/meizi/images/grey.gif 94 http://www.xiaohuar.com/skin/meizi/images/grey.gif 95 http://www.xiaohuar.com/skin/meizi/images/grey.gif 96 http://www.xiaohuar.com/skin/meizi/images/grey.gif 97 http://www.xiaohuar.com/skin/meizi/images/grey.gif 98 http://www.xiaohuar.com/skin/meizi/images/grey.gif 99 http://www.xiaohuar.com/skin/meizi/images/grey.gif100 http://www.xiaohuar.com/skin/meizi/images/grey.gif101 http://www.xiaohuar.com/skin/meizi/images/grey.gif102 http://www.xiaohuar.com/skin/meizi/images/grey.gif103 http://www.xiaohuar.com/skin/meizi/images/grey.gif104 http://www.xiaohuar.com/skin/meizi/images/grey.gif105 http://www.xiaohuar.com/skin/meizi/images/grey.gif106 http://www.xiaohuar.com/skin/meizi/images/grey.gif107 http://www.xiaohuar.com/skin/meizi/images/grey.gif108 http://www.xiaohuar.com/skin/meizi/images/grey.gif109 http://www.xiaohuar.com/skin/meizi/images/grey.gif110 http://www.xiaohuar.com/skin/meizi/images/grey.gif111 http://www.xiaohuar.com/skin/meizi/images/grey.gif112 http://www.xiaohuar.com/skin/meizi/images/grey.gif113 http://www.xiaohuar.com/skin/meizi/images/grey.gif114 http://www.xiaohuar.com/skin/meizi/images/grey.gif115 http://www.xiaohuar.com/skin/meizi/images/grey.gif116 http://www.xiaohuar.com/skin/meizi/images/grey.gif117 http://www.xiaohuar.com/skin/meizi/images/grey.gif118 http://www.xiaohuar.com/skin/meizi/images/grey.gif119 http://www.xiaohuar.com/skin/meizi/images/grey.gif120 http://www.xiaohuar.com/skin/meizi/images/grey.gif121 http://www.xiaohuar.com/skin/meizi/images/grey.gif122 http://www.xiaohuar.com/skin/meizi/images/grey.gif123 http://www.xiaohuar.com/skin/meizi/images/grey.gif124 http://www.xiaohuar.com/skin/meizi/images/grey.gif125 http://www.xiaohuar.com/skin/meizi/images/grey.gif126 http://www.xiaohuar.com/skin/meizi/images/grey.gif127 http://www.xiaohuar.com/skin/meizi/images/grey.gif128 http://www.xiaohuar.com/skin/meizi/images/grey.gif129 http://www.xiaohuar.com/skin/meizi/images/grey.gif130 http://www.xiaohuar.com/skin/meizi/images/grey.gif131 http://www.xiaohuar.com/skin/meizi/images/grey.gif132 http://www.xiaohuar.com/skin/meizi/images/grey.gif133 http://www.xiaohuar.com/skin/meizi/images/grey.gif134 http://www.xiaohuar.com/skin/meizi/images/grey.gif135 http://www.xiaohuar.com/skin/meizi/images/grey.gif136 http://www.xiaohuar.com/skin/meizi/images/grey.gif137 http://www.xiaohuar.com/skin/meizi/images/grey.gif138 http://www.xiaohuar.com/skin/meizi/images/grey.gif139 http://www.xiaohuar.com/skin/meizi/images/grey.gif140 http://www.xiaohuar.com/skin/meizi/images/grey.gif141 http://www.xiaohuar.com/skin/meizi/images/grey.gif142 http://www.xiaohuar.com/skin/meizi/images/grey.gif143 http://www.xiaohuar.com/skin/meizi/images/grey.gif144 [root@yinzhengjie tmp]#
以上代码执行方式结果戳这里哟

   如果你想要的代码扩展性更高,可以将main函数的代码定义成一个函数,这样在主函数调用该函数就好了,代码如下:

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "net/http"12     "log"13     "github.com/PuerkitoBio/goquery"14     "fmt"15     "errors"16 )17 18 func fetch(url string) ([]string,error) {19     var urls []string //定义一个空切片数组20     resp,err := http.Get(url)21     if err != nil {22         log.Fatal(err)23     }24     defer resp.Body.Close()25     if resp.StatusCode != http.StatusOK{26         return nil,errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。27     }28     doc,err := goquery.NewDocumentFromResponse(resp)29     if err != nil {30         log.Fatal(err)31     }32     doc.Find("img").Each(func(i int, s  *goquery.Selection) {33         link,ok := s.Attr("src")34         if ok {35             urls = append(urls,link)  //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。36         }else {37             fmt.Println("抱歉,没有发现该路径。")38         }39 40     })41     return urls,nil42 }43 44 func main() {45     url := "http://m.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。注意这里不要用www,而要用m,因为m是用手机端方式访问。而www是电脑方式访问。46     urls,err := fetch(url)47     if err != nil {48         log.Fatal(err)49     }50     for _,u := range urls {51         fmt.Println(u)52     }53 54 }
1 [root@yinzhengjie tmp]# more goquery.go  2 /* 3 #!/usr/bin/env gorun 4 @author :yinzhengjie 5 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 6 EMAIL:y1053419035@qq.com 7 */ 8  9 package main10 11 import (12         "net/http"13         "log"14         "github.com/PuerkitoBio/goquery"15         "fmt"16         "errors"17 )18 19 func fetch(url string) ([]string,error) {20         var urls []string //定义一个空切片数组21         resp,err := http.Get(url)22         if err != nil {23                 log.Fatal(err)24         }25         defer resp.Body.Close()26         if resp.StatusCode != http.StatusOK{27                 return nil,errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。28         }29         doc,err := goquery.NewDocumentFromResponse(resp)30         if err != nil {31                 log.Fatal(err)32         }33         doc.Find("img").Each(func(i int, s  *goquery.Selection) {34                 link,ok := s.Attr("src")35                 if ok {36                         urls = append(urls,link)  //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。37                 }else {38                         fmt.Println("抱歉,没有发现该路径。")39                 }40 41         })42         return urls,nil43 }44 45 func main() {46         url := "http://m.xiaohuar.com"  //定义一个URl,也就是我们要爬的网站。注意这里不要用www,而要用m,因为m是用手机端方式访问。而w47 ww是电脑方式访问。48         urls,err := fetch(url)49         if err != nil {50                 log.Fatal(err)51         }52         for _,u := range urls {53                 fmt.Println(u)54         }55 56 }57 58 [root@yinzhengjie tmp]# 59 [root@yinzhengjie tmp]# 60 [root@yinzhengjie tmp]# go run goquery.go 61 http://www.xiaohuar.com/d/file/20170715/61110ba027f004fb503ff09cdee44d0c.jpg62 http://www.xiaohuar.com/d/file/20170707/f7ca636f73937e33836e765b7261f036.jpg63 http://www.xiaohuar.com/d/file/20170701/fb18711a6af87f30942d6a19f6da6b3e.jpg64 http://www.xiaohuar.com/d/file/20170628/f3d06ef49965aedbe18286a2f221fd9f.jpg65 http://www.xiaohuar.com/d/file/20170626/0ab1d89f54c90df477a90aa533ceea36.jpg66 http://www.xiaohuar.com/d/file/20170619/e0456729d4dcbea569a1acbc6a47ab69.jpg67 http://www.xiaohuar.com/d/file/20170612/1f6620771e2815d21f37d481fa8311e6.png68 http://www.xiaohuar.com/d/file/20170604/ec3794d0d42b538bf4461a84dac32509.jpg69 http://www.xiaohuar.com/d/file/20170603/e55f77fb3aa3c7f118a46eeef5c0fbbf.jpg70 http://www.xiaohuar.com/d/file/20170603/c34b29f68e8f96d44c63fe29bf4a66b8.jpg71 http://www.xiaohuar.com/d/file/20170529/e5902d4d3e40829f9a0d30f7488eab84.jpg72 http://www.xiaohuar.com/d/file/20170529/8140c4ad797ca01f5e99d09c82dd8a42.jpg73 http://www.xiaohuar.com/d/file/20170528/b352258c83776b9a2462277dec375d0c.jpg74 http://www.xiaohuar.com/d/file/20170527/4a7a7f1e6b69f126292b981c90110d0a.jpg75 http://www.xiaohuar.com/d/file/20170520/dd21a21751e24a8f161792b66011688c.jpg76 http://www.xiaohuar.com/d/file/20170516/6e295fe48c33245be858c40d37fb5ee6.jpg77 http://www.xiaohuar.com/d/file/20170513/6121e3e90ff3ba4c9398121bda1dd582.jpg78 http://www.xiaohuar.com/d/file/20170512/45fc9ed5d92c66f369b66841c58bd183.jpg79 [root@yinzhengjie tmp]#
以上代码执行结果戳这里哟

 

扩展:

  我们在访问一个标签中的源数据,可能获取的路径不是完整路径,那么如何将获取的路径补充其应该对应的绝对路径呢?我们可以简单来实现一下,后期我们可以对其做一个优化。

1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7  8 package main 9 10 import (11     "errors"12     "fmt"13     "github.com/PuerkitoBio/goquery"14     "log"15     "net/http"16     "net/url"17     "os"18     "strings"19 )20 21 func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。22     var urls []string //定义一个空切片数组23     resp, err := http.Get(url)24     if err != nil {25         log.Fatal(err)26     }27     defer resp.Body.Close()28     if resp.StatusCode != http.StatusOK {29         return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。30     }31     doc, err := goquery.NewDocumentFromResponse(resp)32     if err != nil {33         log.Fatal(err)34     }35     doc.Find("img").Each(func(i int, s *goquery.Selection) {36         link, ok := s.Attr("src")37         if ok {38             urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。39         } else {40             fmt.Println("抱歉,没有发现该路径。")41         }42 43     })44     return urls, nil45 }46 47 func Clean_urls(root_path string, picture_path []string) []string {48     var Absolute_path []string //定义一个绝对路径数组。49     url_info, err := url.Parse(root_path)50     if err != nil {51         log.Fatal(err)52     }53     Scheme := url_info.Scheme //获取到链接的协议54     //fmt.Println("使用的协议是:",Scheme)55     Host := url_info.Host //获取链接的主机名56     //fmt.Println("主机名称是:",Host)57     Path := url_info.Path                         //获取到链接的相对路径58     Path_Directory := strings.Split(Path, "/")[1] //获取到存放文件的目录的路径“Path_Directory”。59     //fmt.Println("存放文件的目录是:",Path_Directory)60     for _, souce_path := range picture_path {61         if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。62 63         } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)64             souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。65         } else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。66             souce_path = Scheme + "://" + Host + souce_path67         } else {68             souce_path = Scheme + "://" + Host + "/" + Path_Directory + "/" + souce_path69         }70         Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。71     }72     return Absolute_path //最终返回处理后的每个链接的绝对路基。73 }74 75 func main() {76     root_path := os.Args[1]               //定义一个URl,也就是我们要爬的网站。77     picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。78     if err != nil {79         log.Fatal(err)80     }81 82     Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。83 84     for _, Picture_absolute_path := range Absolute_path {85         fmt.Println(Picture_absolute_path) //最终我们会得到一个图片的完整路径,我们可以对这个路径进行下载,压缩,加密等等操作。86     }87 }

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的文章
GPU编程(二): GPU架构了解一下!
查看>>
HBase核心知识点总结
查看>>
Django RESTFramework——更新数据 (5)
查看>>
iOS目录解析 彻底搞懂iOS App的目录结构
查看>>
深入浅出JDK动态代理(二)
查看>>
前端实现获取浏览器flash版本号
查看>>
Hybrid小技巧:通过js调用原生对话框(Android)
查看>>
日志服务Python消费组实战(二):实时分发数据
查看>>
从几道面试题看对象的初始化
查看>>
盛极而衰,互联网体育是伪风口还是真趋势?
查看>>
14-《ARKit by Tutorials》读书笔记1:开始入门
查看>>
[MetalKit]33-Ambient-Occlusion-in-Metal环境光遮蔽
查看>>
图解JavaScript算法排序
查看>>
Flask环境搭建(自己学习用)
查看>>
iOS逆向之旅(进阶篇) — HOOK(Method Swizzling)
查看>>
Javascript之正则表达式的学习笔记
查看>>
Hadoop 学习系列(三)之 YARN 详细解析
查看>>
QPM 之悬浮窗助力性能优化
查看>>
YYCache 源码学习(一):YYMemoryCache
查看>>
ios 原生骨架动画库
查看>>