Goの知識-私たちは、マルチスレッドず婊でWebペヌゞのグラバヌを䜜成したす

おそらく誰もがGoogleチヌムからGo蚀語に぀いお聞いたでしょう。 しかし、誰もがそれを詊したわけではなく、Go Gophersず話すこずは、私自身の経隓から最近芋たように、喜びの海です。

人生の䟋で新しい蚀語に粟通するのが最も楜しいので、ためらうこずなく、私は「人生から、最も優先床の高い」に出䌚った最初のタスクを取りたした。



むンタヌネットhttp://vpustotu.ruには 、誰でも苊痛に぀いお匿名で話すこずができるサむトがありたす。 すべおのステヌトメント将来的には「匕甚」ず呌びたすはたずモデレヌションバショルグの「深”」に類䌌に分類されたす。 モデレヌトペヌゞ http://vpustotu.ru/moderation/ には、ランダムな匕甚、投祚リンク、および同じペヌゞに぀ながる「その他」リンクが衚瀺されたす。 クリック、それはすべお非垞に簡単です。



そしお、タスクが発生したした -緊急に、暗闇の䞭で、さらなる秘密の研究のために節床に関するすべおの匕甚の完党なダンプをアップロヌドする必芁がありたした。 タスクの䞖俗的な䟡倀や癜痎の皋床は評䟡したせんが、技術的な芳点から怜蚎したす。



モデレヌトセクションには特定の芋積もりぞの盎接リンクはありたせん。新しい芋積もりを取埗する唯䞀の方法は、ペヌゞを曎新するこずですたたは、「詳现」リンクをクリックしたす。 さらに、繰り返しはかなり可胜です。これは、数分間の積極的なクリックの埌、簡単に怜出されたす。



したがっお、次のプログラムが必芁です。







すべおの匕甚笊が読み蟌たれおいるかどうかわからないのは論理的ですが、これは、連続しお繰り返し受信される倚数の匕甚笊によっお間接的に掚枬できたす。 したがっお、以䞋を補足したす。







たあ、すべおが明らかなようです。 プログラムが2぀のファむルを保持するようにしたす-匕甚笊ずこれらの匕甚笊のハッシュを䜿甚しお、繰り返さないようにし、各実行の開始時にファむルを再読み蟌みしたす。 それでは、額でctrl-cを受け取るか、䞀定の繰り返し回数に満たなくなるたで、サむクルで圌はペヌゞを解析し、たすたす倚くの啓瀺を匕き出したす。 タスクは明確で、蚈画がありたす-行こう



お気に入りの゚ディタヌを開き、新しいプロゞェクトを䜜成したす。 最初は、空のプログラムのコヌドは次のようになりたす。

package main import ( "fmt" ) func main() { fmt.Println("Hello World!") }
      
      





私たちはプログラムの順番でリストを調べ始めたす



「ストリヌム」の数、ファむル名など、いく぀かのパラメヌタヌがありたす。 組み蟌みの「 flag 」パッケヌゞは、コマンドラむンからこのすべおの矎しさを匕き出すのに圹立ちたす。 むンポヌトリストに远加し、同時にパラメヌタヌ倉数を宣蚀したす。

 import ( "flag" "fmt" ) var ( WORKERS int = 2 //- "" REPORT_PERIOD int = 10 //  () DUP_TO_STOP int = 500 //    HASH_FILE string = "hash.bin" //   QUOTES_FILE string = "quotes.txt" //   )
      
      





Goの倉数の型はその名前の埌に蚘述されたすが、これは少し珍しい堎合がありたす。 たた、すべおが䟿利で䞀箇所に収たるように、デフォルト倀もすぐに初期化したす。



匕数の解析などは通垞init関数で行われたす。

 func init() { //  : flag.IntVar(&WORKERS, "w", WORKERS, " ") flag.IntVar(&REPORT_PERIOD, "r", REPORT_PERIOD, "  ()") flag.IntVar(&DUP_TO_STOP, "d", DUP_TO_STOP, "-   ") flag.StringVar(&HASH_FILE, "hf", HASH_FILE, " ") flag.StringVar(&QUOTES_FILE, "qf", QUOTES_FILE, " ") //    flag.Parse() }
      
      





UPD コメントで Forked Habraiserは、initでflag.Parse関数を呌び出すのが悪い習慣である理由を䞀般的に説明したした。 圌に感謝したす-これからmainでそれを行うこずをお勧めしたすが、この䟋は「fu do this」ずいう教蚓ずしおここに残りたす。



フラグパッケヌゞの関数IntVar 数倀甚およびStringVar 文字列甚を䜿甚したす-コマンドラむン匕数から指定されたキヌを読み取り、倉数に枡したす。 キヌが指定されおいない堎合、デフォルト倀が䜿甚されたす。 関数の構文は同じです

 flag.StringVar( &_,  . ,    ,  )
      
      





関数が必芁に応じお倉曎できるように、倉数蚘号にポむンタヌを枡すこずに泚意しおください。 パラメヌタ「キヌの説明」も興味深いです。実際には、フラグパッケヌゞは「-h」キヌでアクセス可胜な匕数の凡䟋を自動的に䜜成したす。 今すぐプログラムを起動しお、これを芋るこずができたす

 C:\Go\src\habratest>habratest.exe -h Usage of habratest.exe: -d=500: -    -hf="hash.bin":   -qf="quotes.txt":   -r=10:   () -w=2:  
      
      





さらに、蚈画によるず、プログラムの起動時にハッシュがロヌドされたすが、埌でロヌドするものがあるずきにこれに戻るこずをお勧めしたす。 今のずころ、すべおの起動が最初であるこずを想像しおください。



次に、プログラム自䜓に぀いお考えおみたしょう。ペヌゞを読み取り、それを分解し、結果を蚘録し、進行状況を分析する必芁がありたす。 はい、そしおいく぀かの「スレッド」ですぐに刀断したす-スレッドではなくスレッドず呌びたす。たあ、念のため。 ハ



Goのマルチタスクはgoroutinesによっお実装されたす。぀たり、その前にある「go」キヌワヌドを眮換するこずにより、「バックグラりンド」で任意の関数を実行できたす。それは開始し、プログラムはすぐに続行したす。すぐに別のこずができたす

 //   ... func GadgetUmbrella() { //...   ... } //...       ,   : go GadgetUmbrella() fmt.Println(" !") //          ,      GadgetUmbrella()
      
      





䞀般に、ゎルチンは玔粋なストリヌムではなく、すべおがより興味深いものですが、そのようなこずは明らかに私たちの割り圓おの範囲を超えおいたす。



無限ルヌプでペヌゞを読み蟌んで解析し、完成した匕甚を䞎える関数を別の「ストリヌム」に入れるこずを提案したす。それを「グラバヌ」ず呌びたしょう。 いく぀かのコピヌでバックグラりンドで自分自身を回転させおください。メむンプログラムから私たちはこれらの匕甚をサむクルでキャッチし、それらを䜿甚しお必芁なこずを行い、特定の状況の堎合には䜜業をオフにしたす。 特に、すでにマルチスレッドプログラミングの経隓があり、共有ず同期の倚くのニュアンスに぀いお恐怖で悲鳎を䞊げる準備ができおいる人にずっおは、疑わしいほど簡単に聞こえたす。



しかし実際には、Goのもう1぀の優れた機胜はチャンネルであるため、すべおが本圓にシンプルです。 簡単に蚀えば、チャネルは、䞀方の偎で倀がスロヌされ、もう䞀方の偎でキャッチされるパむプずしお簡単に想像できたす。 チャンネルは兞型的なものであり、初期化が必芁です-簡単に次のように操䜜したす

 func send( c chan int) {  <- 15 //     } func main() { ch := make(chan int) //      (int) go send(ch) b:= <-ch //    fmt.Println(b) }
      
      





私はgochを曞いただけでなく、shを曞いたこずに泚意しおください。 実際には、デフォルトでは、チャネルからのデヌタの送信ず受信は、もう䞀方の端がデヌタを凊理する準備ができるたで、それらを匕き起こしたサブルヌチンをブロックしたす。 これは非垞に優れた同期ツヌルです。

「 go 」を削陀するず、 sendがメむンスレッドで実行され、ブロックされたす。これは、誰かが15番をピックアップする準備ができるたで埅機するためです。次の行でピックアップしたすが、制埡を移したした。 デッドロック、誰もが悲しい。

ただし、「 go send 」の堎合、 sendはブロックされたすが、ストリヌム内で、別の読み取りを行うたで埅機するため、すべおがスムヌズに実行されたす -これはすぐに行われ、デヌタ亀換が行われたす成功したした。

たた、 send関数でチャネルぞの曞き蟌みを削陀するず、 b= <-c行のメむン関数は、受信するものがないため、逆に無効になりたす。

チャンネルはそれぞれファヌストクラスのオブゞェクトであり、䟿利なように䜜成、転送、返华、割り圓おが可胜です。



デヌタは「グラバヌ」ずしお送信され、メむンサむクルで受信されたす。



ペヌゞコヌド内の匕甚は、単にクラス「fi_text」を持぀divにありたす。 「グラバヌ」で取埗するには、 goqueryパッケヌゞを䜿甚したす。これにより、htmlペヌゞを解析し、jQueryのセレクタヌを䜿甚しおそのコンテンツにアクセスできたす。 このパッケヌゞは暙準パッケヌゞには含たれおいないため、最初にむンストヌルする必芁がありたす。

 #  : go get github.com/opesun/goquery
      
      





たた、コヌドのむンポヌトセクションに、パッケヌゞ「github.com/opesun/goquery」、「strings」、「time」を远加したす。遅延のために埌者が必芁です。リク゚ストでサヌバヌを垞にプルするわけではありたせんそう、しかし、あなたは私を理解しおいたす

 import ( "flag" "fmt" "github.com/opesun/goquery" "strings" "time" )
      
      







ポむントに近づいお、「グラバヌ」コヌドを蚘述したす。

 func grab() <-chan string { //  ,        string c := make(chan string) for i := 0; i < WORKERS; i++ { //       - worker'o go func() { for { //     x, err := goquery.ParseUrl("http://vpustotu.ru/moderation/") if err == nil { if s := strings.TrimSpace(x.Find(".fi_text").Text()); s != "" { c <- s //     } } time.Sleep(100 * time.Millisecond) } }() } fmt.Println(" : ", WORKERS) return c }
      
      





コヌドは非垞にシンプルで、コメントはほずんど必芁ありたせん。

クロヌゞャヌを䜿甚しお、 grabによっお返されるチャネルにデヌタを垞に送信する匿名関数を実行する必芁な数のgourutinを䜜成したす。 このようなパタヌンはゞェネレヌタヌず呌ばれたす。

䜕らかの理由で、 x.Find "。Fi_text"。Textは、先頭ず末尟にスペヌスがある目的の芁玠のコンテンツを返したので、ためらうこずなく、暙準の文字列モゞュヌルのTrimSpace関数でクリアしたす。



ゞェネレヌタヌの準備ができたので、main関数を倉曎するこずで、ゞェネレヌタヌが機胜するこずを確認できたす。

 func main() { quote_chan := grab() for i := 0; i < 5; i++ { // 5    fmt.Println(<-quote_chan, "\n") } }
      
      





蚈画通りにすべおが進んでいるこずがわかりたす。啓瀺が私たちのチャンネルに広範に流れ蟌んでいたす





次に、蚈画に埓っお倀を収集するメむンサむクルに぀いお考えおみたしょう。 芁件ず芁望に応じお、次のサむクルが必芁です。







コレクションに関する問題はもうありたせん。ハッシュを決定したす。

簡単にするために、匕甚からmd5を取埗するこずをお勧めしたす。 ハッシュをマップ キヌず倀のストレヌゞの組み蟌み構造に保存するので、すばやく簡単に怜玢できたす。 䞀意性をチェックし、統蚈ず繰り返しをカりントしたす-これは技術の問題です。 Goでファむルを操䜜するこずは他の蚀語ず倉わらないため、問題もありたせん。



時間レポヌトは、暙準の「 時間 」パッケヌゞのティッカヌを䜿甚しお実装できたす。 これは、指定された期間埌にトリガヌされ、特定の倀をチャネルに送信する最も単玔なタむマヌです。デヌタを受信するず、チャネルを監芖しお統蚈を衚瀺するだけです。



そしお、「 os / signal 」パッケヌゞを䜿甚しおコマンドを完了させるためにキャッチしたす。これにより、むベント通知をチャネルに送信する特定のシグナルに通知機胜を掛けるこずができたす。



蚈画は準備が敎っおいたすが、1぀の譊告がありたすデヌタを受信する3぀の異なるチャネルを取埗したすが、以前は読み取りを埅機しおいる間、ストリヌムがブロックされるため、䞀床に最倧1぀のチャネルの情報を埅機できるず蚀われおいたした。

しかし、goはCPU時間を無駄に消費するわけではありたせん。もう1぀の優れたツヌルはselectコンストラクトです。

Selectを䜿甚するず、無制限の数のチャネルからのデヌタを期埅でき、凊理䞭に次のデヌタが到着した堎合にのみ実行をブロックしたす。 必芁なもの



コヌドに取り掛かりたしょう たず、必芁なパッケヌゞをむンポヌトセクションに远加したす。これで、次のようになりたす。

 import ( "flag" "fmt" "github.com/opesun/goquery" "strings" "time" //,        : "io" "os" "os/signal" //   -   : "crypto/md5" "encoding/hex" )
      
      





はい、たくさんありたす... Goの静的リンクを考えるず、実行可胜ファむルのサむズはすばらしいはずです

しかし、これは悲しい時ではありたせん。ハッシュ甚のストレヌゞを宣蚀したす

 var ( ... used map[string]bool = make(map[string]bool) //map        ,    -  . )
      
      





そしお最埌に、メむン関数は次のようになりたす。

 func main() { //   ... quotes_file, err := os.OpenFile(QUOTES_FILE, os.O_APPEND|os.O_CREATE, 0666) check(err) defer quotes_file.Close() //...    hash_file, err := os.OpenFile(HASH_FILE, os.O_APPEND|os.O_CREATE, 0666) check(err) defer hash_file.Close() // Ticker          ticker := time.NewTicker(time.Duration(REPORT_PERIOD) * time.Second) defer ticker.Stop() // ,     ,     ... key_chan := make(chan os.Signal, 1) signal.Notify(key_chan, os.Interrupt) //...       hasher := md5.New() //    quotes_count, dup_count := 0, 0 // , ! quotes_chan := grab() for { select { case quote := <-quotes_chan: // ""  : quotes_count++ // ,     : hasher.Reset() io.WriteString(hasher, quote) hash := hasher.Sum(nil) hash_string := hex.EncodeToString(hash) //    if !used[hash_string] { //   -    ,        used[hash_string] = true hash_file.Write(hash) quotes_file.WriteString(quote + "\n\n\n") dup_count = 0 } else { //  -   ,    ? if dup_count++; dup_count == DUP_TO_STOP { fmt.Println("  ,  .  : ", len(used)) return } } case <-key_chan: //     : fmt.Println("CTRL-C:  .  : ", len(used)) return case <-ticker.C: //, ,        fmt.Printf(" %d /  %d (%d /) \n", len(used), dup_count, quotes_count/REPORT_PERIOD) quotes_count = 0 } } }
      
      





コヌドは完党に透過的であり、ファむルを開くこずのみを説明したす。

check関数は暙準ではありたせん-これは、ファむルを開いお゚ラヌの結果を確認するために䟿利です。 ここにコヌドがあり、 mainの前のどこかに眮きたす

 func check(e error) { if e != nil { panic(e) } }
      
      





もう1぀の興味深い点は、「すぐに」ファむルを閉じるようにするこずですが、その埌は䜜業を続けたす。

 defer quotes_file.Close() ... defer hash_file.Close() ... //      : defer ticker.Stop()
      
      





䞀番䞋の行は、関数をdeferで開始するこずにより、その実行を延期するこずです。぀たり、関数はdefer呌び出しが完了した関数の前぀たり、「芪」が戻る盎前にのみ実行されたす。



「ひどく必芁なミッション」の実珟をすでに開始しお楜しむこずができたすが、もう1぀小さな詳现がありたす。プログラムを再床実行したいが、結果のファむルに重耇が芋られないように、ファむルからハッシュを読み取る関数を蚘述する必芁がありたす。 これを行う1぀の方法を次に瀺したす。

 func readHashes() { //    if _, err := os.Stat(HASH_FILE); err != nil { if os.IsNotExist(err) { fmt.Println("   ,   .") return } } fmt.Println(" ...") hash_file, err := os.OpenFile(HASH_FILE, os.O_RDONLY, 0666) check(err) defer hash_file.Close() //    16  -    : data := make([]byte, 16) for { n, err := hash_file.Read(data) //n    ,  err - ,   . if err != nil { if err == io.EOF { break } panic(err) } if n == 16 { used[hex.EncodeToString(data)] = true } } fmt.Println(".  : ", len(used)) }
      
      





それだけです。mainの先頭にreadHashes呌び出しを配眮するこずを忘れないでください

 func main() { readHashes() ...
      
      







できた 戊闘開始





仕事の結果





ファむルの䜜成も機胜し、再起動時にハッシュを再読み蟌みしたす。 圌女は呜什に立ち止たり、圌女自身もできる。 もちろん、実行可胜ファむルは少し倧きいですが、それに合わせおください。

倜間に非垞に重芁なデヌタのナンセンスをアンロヌドするためのプログラムは準備が敎っおおり、それから必芁なこずを実行したす。

もちろん、数倚くの改善点がありたすが、このコヌドは単玔な戊闘ミッションには十分です



Pastbin.comのプログラムコヌド党䜓



Goずその機胜に興味がある堎合は、次のサむトをご芧ください。



tour.golang.org-蚀語のオンラむンツアヌ。ブラりザでコヌドを盎接線集するこずは非垞に゚キサむティングです。



gobyexample.com-兞型的なタスクの䟋



golang.org/ref/spec-蚀語仕様



golang.org/doc/effective_go.html- 「そしお今、私たちはこのゎミすべおで離陞しようずしたす」ずいうトピックに関する蚘事-正しくGoを離陞しお飛ぶ方法



Google Goグルヌプ-「 https://groups.google.com/forum/#!forum/golang-nuts 」および「 https://groups.google.com/forum/#!forum/golang-ru 」



godoc.org -Goパッケヌゞずそのドキュメント組み蟌みずサヌドパヌティの䞡方を怜玢するこずは、アンチバむクの玠晎らしいこずです



頑匵っお



All Articles