Multithreading 多个goroutine访问/修改列表/映射

Multithreading 多个goroutine访问/修改列表/映射,multithreading,go,concurrency,goroutine,Multithreading,Go,Concurrency,Goroutine,我正在尝试实现一个多线程爬虫程序,使用go-lang作为学习该语言的示例任务 它应该扫描页面,跟踪链接并保存它们 为了避免重复,我尝试使用map来保存我已经保存的所有URL 同步版本工作得很好,但是我在尝试使用goroutines时遇到了麻烦 我尝试使用互斥体作为映射的同步对象,使用通道作为协调goroutine的方式。但很明显,我对它们没有清晰的理解 问题是我有许多重复条目,因此我的地图存储/检查无法正常工作 这是我的密码: package main import ( "fmt"

我正在尝试实现一个多线程爬虫程序,使用go-lang作为学习该语言的示例任务

它应该扫描页面,跟踪链接并保存它们

为了避免重复,我尝试使用map来保存我已经保存的所有URL

同步版本工作得很好,但是我在尝试使用goroutines时遇到了麻烦

我尝试使用互斥体作为映射的同步对象,使用通道作为协调goroutine的方式。但很明显,我对它们没有清晰的理解

问题是我有许多重复条目,因此我的地图存储/检查无法正常工作

这是我的密码:

package main

import (
    "fmt"
    "net/http"
    "golang.org/x/net/html"
    "strings"
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
    "io/ioutil"
    "runtime/debug"
    "sync"
)

const maxDepth = 2;

var workers = make(chan bool)

type Pages struct {
    mu sync.Mutex
    pagesMap map[string]bool
}

func main() {
    var pagesMutex Pages
    fmt.Println("Start")
    const database = "gotest"
    const user = "root"
    const password = "123"

    //open connection to DB
    con, err := sql.Open("mymysql", database + "/" + user + "/" + password)
    if err != nil { /* error handling */
        fmt.Printf("%s", err)
        debug.PrintStack()
    }

    fmt.Println("call 1st save site")
    pagesMutex.pagesMap = make(map[string]bool)
    go pagesMutex.saveSite(con, "http://golang.org/", 0)

    fmt.Println("saving true to channel")
    workers <- true

    fmt.Println("finishing in main")
    defer con.Close()
}


func (p *Pages) saveSite(con *sql.DB, url string, depth int) {
    fmt.Println("Save ", url, depth)
    fmt.Println("trying to lock")
    p.mu.Lock()
    fmt.Println("locked on mutex")
    pageDownloaded := p.pagesMap[url] == true
    if pageDownloaded {
        p.mu.Unlock()
        return
    } else {
        p.pagesMap[url] = true
    }
    p.mu.Unlock()

    response, err := http.Get(url)
    if err != nil {
        fmt.Printf("%s", err)
        debug.PrintStack()
    } else {
        defer response.Body.Close()

        contents, err := ioutil.ReadAll(response.Body)
        if err != nil {
            if err != nil {
                fmt.Printf("%s", err)
                debug.PrintStack()
            }
        }

        _, err = con.Exec("insert into pages (url) values (?)", string(url))
        if err != nil {
            fmt.Printf("%s", err)
            debug.PrintStack()
        }
        z := html.NewTokenizer(strings.NewReader((string(contents))))

        for {
            tokenType := z.Next()
            if tokenType == html.ErrorToken {
                return
            }

            token := z.Token()
            switch tokenType {
            case html.StartTagToken: // <tag>

                tagName := token.Data
                if strings.Compare(string(tagName), "a") == 0 {
                    for _, attr := range token.Attr {
                        if strings.Compare(attr.Key, "href") == 0 {
                            if depth < maxDepth  {
                                urlNew := attr.Val
                                if !strings.HasPrefix(urlNew, "http")  {
                                    if strings.HasPrefix(urlNew, "/")  {
                                        urlNew = urlNew[1:]
                                    }
                                    urlNew = url + urlNew
                                }
                                //urlNew = path.Clean(urlNew)
                                go  p.saveSite(con, urlNew, depth + 1)

                            }
                        }
                    }

                }
            case html.TextToken: // text between start and end tag
            case html.EndTagToken: // </tag>
            case html.SelfClosingTagToken: // <tag/>

            }

        }

    }
    val := <-workers
    fmt.Println("finished Save Site", val)
}
主程序包
进口(
“fmt”
“net/http”
“golang.org/x/net/html”
“字符串”
“数据库/sql”
_“github.com/ziutek/mymysql/godrv”
“io/ioutil”
“运行时/调试”
“同步”
)
常数maxDepth=2;
var工人=制造(chan bool)
类型页结构{
mu-sync.Mutex
页面映射[字符串]布尔
}
func main(){
var pagesMutex页面
fmt.Println(“开始”)
const database=“gotest”
const user=“root”
const password=“123”
//打开到数据库的连接
con,err:=sql.Open(“mymymysql”,数据库+“/”+用户+“/”+密码)
如果err!=nil{/*错误处理*/
fmt.Printf(“%s”,错误)
debug.PrintStack()
}
fmt.Println(“调用第一个保存站点”)
pagesMutex.pagesMap=make(映射[string]bool)
转到pagesMutex.saveSite(con,“http://golang.org/", 0)
fmt.Println(“保存真实到通道”)

workers您有两种选择,对于一个小而简单的实现,我建议将map上的操作分离到一个单独的结构中

// Index is a shared page index
type Index struct {
    access sync.Mutex
    pages map[string]bool
}

// Mark reports that a site have been visited
func (i Index) Mark(name string) {
    i.access.Lock()
    i.pages[name] = true
    i.access.Unlock()
}

// Visited returns true if a site have been visited
func (i Index) Visited(name string) bool {
    i.access.Lock()
    defer i.access.Unlock()

    return i.pages[name]
}
然后,添加另一个结构,如下所示:

// Crawler is a web spider :D
type Crawler struct {
    index Index
    /* ... other important stuff like visited sites ... */
}

// Crawl looks for content
func (c *Crawler) Crawl(site string) {
    // Implement your logic here 
    // For example: 
    if !c.index.Visited(site) {
        c.index.Mark(site) // When marked
    }
}
sameIndex := Index{pages: make(map[string]bool)}
asManyAsYouWant := Crawler{sameIndex, 0} // They will share sameIndex
这样,您就可以让事情变得清晰明了,可能需要编写更多的代码,但绝对更具可读性。您需要像这样对爬虫进行实例:

// Crawler is a web spider :D
type Crawler struct {
    index Index
    /* ... other important stuff like visited sites ... */
}

// Crawl looks for content
func (c *Crawler) Crawl(site string) {
    // Implement your logic here 
    // For example: 
    if !c.index.Visited(site) {
        c.index.Mark(site) // When marked
    }
}
sameIndex := Index{pages: make(map[string]bool)}
asManyAsYouWant := Crawler{sameIndex, 0} // They will share sameIndex

如果您想更进一步地使用高级解决方案,那么我建议使用生产者/消费者体系结构。

如果您将
结果
通道传递到每个例程中,则不需要互斥,将您在其上爬行的url发送回调用作用域并附加到那里。我不建议尝试与您共享资源的访问权限在例程中,将数据传递回没有并发性的上下文要容易得多。只要我的2美分。你也可以使用互斥体来完成。如果你将所有的映射访问分解成单独的方法,然后在每个方法中使用
Lock();defer Unlock()
,也更容易进行推理。也许还值得探索一个新的方法(@IanMcMahon,RWMutex稍微慢一点,更值得考虑,而且通常对地图没有好处,因为地图索引非常快)