1. 程式人生 > >利用golang併發下載股票資料(一)

利用golang併發下載股票資料(一)

先貼上程式碼

//批量獲取雅虎股票資料。
package main

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"
)

const (
	UA = "Golang Downloader from Ijibu.com"
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) //設定cpu的核的數量,從而實現高併發
	c := make(chan bool)
	fh, ferr := os.Open("./shang.ini")   //上證裡面的所有股票程式碼,每一行就是一個股票程式碼。
	if ferr != nil {
		return
	}
	defer fh.Close()
	inputread := bufio.NewReader(fh)

	for i := 0; i < 1162; i++ {
		input, _ := inputread.ReadString('\n')
		go getShangTickerTables(c, i, strings.TrimSpace(input))
	}

	<-c

	fmt.Println("main ok")
}

func getShangTickerTables(c chan bool, n int, code string) {
	fileName := "./data/sh/" + code + ".csv"
	f, err := os.OpenFile(fileName, os.O_CREATE, 0666) //其實這裡的 O_RDWR應該是 O_RDWR|O_CREATE,也就是檔案不存在的情況下就建一個空檔案,但是因為windows下還有BUG,如果使用這個O_CREATE,就會直接清空檔案,所以這裡就不用了這個標誌,你自己事先建立好檔案。
	if err != nil {
		panic(err)
	}
	stat, err := f.Stat() //獲取檔案狀態
	if err != nil {
		panic(err)
	}
	defer f.Close()
	urls := "http://table.finance.yahoo.com/table.csv?s=" + code + ".ss"
	var req http.Request
	req.Method = "GET"
	req.Close = true
	req.URL, err = url.Parse(urls)

	if err != nil {
		panic(err)
	}
	header := http.Header{}
	header.Set("Range", "bytes="+strconv.Itoa(int(stat.Size()))+"-")
	header.Set("User-Agent", UA)
	req.Header = header
	resp, err := http.DefaultClient.Do(&req)
	if err != nil {
		panic(err)
	}

	io.Copy(f, resp.Body)

	if n == 1161 {
		c <- true
	}
	defer resp.Body.Close()
}

上面的程式碼存在2個嚴重的問題

1.不管在windows還是在linux下執行都會出錯。

以linux為例講,最開始是錯誤如下:

runtime/cgo: pthread_create failed: Resource temporarily unavailable
SIGABRT: abort
PC=0xca2424

goroutine 1 [chan receive]:
main.main()
	/root/go/src/ijibu/gettrackers/gettrackers.go:35 +0x13d

goroutine 2 [syscall]:

goroutine 4 [select]:
net/http.(*Transport).getConn(0x18475120, 0x1850b8a0, 0x1850b8a0, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/transport.go:407 +0x1ef
net/http.(*Transport).RoundTrip(0x18475120, 0x18507e00, 0x5, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/transport.go:181 +0x260
net/http.send(0x18507e00, 0x184629c0, 0x18475120, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:166 +0x2b8
net/http.(*Client).send(0x82e2240, 0x18507e00, 0x34, 0x18512058, 0xb730ef40, ...)
	/usr/local/go/src/pkg/net/http/client.go:100 +0xa5
net/http.(*Client).doFollowingRedirects(0x82e2240, 0x18507e00, 0x821244c, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:282 +0x4d0
net/http.(*Client).Do(0x82e2240, 0x18507e00, 0xa, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:129 +0x7b
main.getShangTickerTables(0x18466270, 0x0, 0x18400418, 0x6)
	/root/go/src/ijibu/gettrackers/gettrackers.go:64 +0x2a7
created by main.main
	/root/go/src/ijibu/gettrackers/gettrackers.go:32 +0x117

goroutine 5 [select]:
net/http.(*Transport).getConn(0x18475120, 0x1850b220, 0x1850b220, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/transport.go:407 +0x1ef
net/http.(*Transport).RoundTrip(0x18475120, 0x18c43150, 0x5, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/transport.go:181 +0x260
net/http.send(0x18c43150, 0x184629c0, 0x18475120, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:166 +0x2b8
net/http.(*Client).send(0x82e2240, 0x18c43150, 0x34, 0x18ae6ec8, 0xb730df40, ...)
	/usr/local/go/src/pkg/net/http/client.go:100 +0xa5
net/http.(*Client).doFollowingRedirects(0x82e2240, 0x18c43150, 0x821244c, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:282 +0x4d0
net/http.(*Client).Do(0x82e2240, 0x18c43150, 0xa, 0x0, 0x0, ...)
	/usr/local/go/src/pkg/net/http/client.go:129 +0x7b
main.getShangTickerTables(0x18466270, 0x1, 0x18400428, 0x6)
	/root/go/src/ijibu/gettrackers/gettrackers.go:64 +0x2a7
created by main.main
	/root/go/src/ijibu/gettrackers/gettrackers.go:32 +0x117

runtime/cgo: pthread_create failed: Resource temporarily unavailable
google了一下是DNS查詢的問題,vi /etc/hosts,加入
67.195.146.181table.finance.yahoo.com
即可解決上面的問題

2.根本沒有按著預期請求完所有的資料

即,雖然我迴圈了1161次,預期是呼叫查詢介面1162次,然後寫入資料,但是並沒有執行完1162次,實際只執行了100多次,因為其他檔案都是為空。

鄙人分析,是goroutine的執行機制有關。參考《Go語言程式設計》前言《併發與分散式》部分,原話如下:“Go語言在語言級別支援協程,叫goroutine。Go語言標準庫提供的所有系統呼叫(syscall)操作,當然也包括所有的同步IO操作,都會出讓CPU給其它的goroutine”,由此可見外層迴圈完了1162次,產生了1162個goroutine,這些goroutine有go語言的引擎負責自己排程執行,假設第1162個goroutine被較早的排程,此時channel就會被置為true,此時main就執行完了。如果是這種情況,那麼

os.OpenFile(fileName, os.O_CREATE, 0666)

可能就執行不了1162次,那麼就不能建立1162個檔案。但是執行顯示的確產生了1162個檔案。說明goroutine的排程還是有順序的。但是在執行每個goroutine(即getShangTickerTables函式)時,由於有網路操作,所以此時會出讓CPU給其它的goroutine。由於網路IO比較費時,所以才會出現上面的情況,每個goroutine都執行了一部分,然後由於網路IO被切換,channel寫入true,實際上每個