{"id":221,"date":"2010-05-21T06:54:34","date_gmt":"2010-05-21T13:54:34","guid":{"rendered":"http:\/\/www.connorgarvey.com\/blog\/?p=221"},"modified":"2010-05-21T06:57:45","modified_gmt":"2010-05-21T13:57:45","slug":"a-threaded-go-language-application-with-semaphores","status":"publish","type":"post","link":"https:\/\/connorgarvey.com\/blog\/?p=221","title":{"rendered":"A threaded Go language application with semaphores"},"content":{"rendered":"<p>After hearing about <a href=\"http:\/\/golang.org\/\">Go<\/a> and its <a href=\"http:\/\/golang.org\/doc\/effective_go.html#concurrency\">love of parallelism<\/a>, I wanted to give it a try.  I couldn&#8217;t think of any small projects that needed to be threaded until recently.\u00a0 My employer uses an RPM system, but I&#8217;m using Ubuntu, which requires deb files.\u00a0 Searching through the RPMs in a browser is a pain, so I wanted a small script that would search for me.\u00a0 Here&#8217;s the script with server names changed.<\/p>\n<pre style=\"padding-left: 30px;\">package main\r\n\r\nimport (\r\n  \"bufio\"\r\n  \"fmt\"\r\n  \"http\"\r\n  \"os\"\r\n  \"regexp\"\r\n  \"strings\"\r\n)\r\n\r\nvar servers = []string {\r\n  \"http:\/\/www.google.com\/search?client=ubuntu&amp;channel=fs&amp;q=go+language&amp;ie=utf-8&amp;oe=utf-8\",\r\n  \"http:\/\/golang.org\/\",\r\n  \"http:\/\/golang.org\/doc\/go_tutorial.html\"}\r\n\r\nvar maxOpenRequests int = 3\r\n\r\nvar rpmRegexp = regexp.MustCompile(\"href=\\\"[^\\\"]+\\\"\")\r\n\r\nfunc main() {\r\n  search := getSearchTerm()\r\n  out := make(chan string)\r\n  done := make(chan int)\r\n  go printer(out)\r\n  for _, url := range servers {\r\n    go searchURL(search, url, out, done)\r\n  }\r\n  for i := 0; i &lt; len(servers); i++ {\r\n    &lt;- done\r\n  }\r\n}\r\n\r\nfunc getSearchTerm() string {\r\n  args := os.Args\r\n  if len(args) != 2 {\r\n    die(\"Please enter one search term\")\r\n  }\r\n  return args[1]\r\n}\r\n\r\nfunc die(message string) {\r\n  fmt.Println(message)\r\n  os.Exit(1)\r\n}\r\n\r\nfunc printer(out chan string) {\r\n  for {\r\n    fmt.Println(&lt;- out)\r\n  }\r\n}\r\n\r\nvar requestSemaphore = make(chan int, maxOpenRequests) \/\/ Integer chanel with a maximum queue size\r\n\r\nfunc searchURL(search string, url string, out chan string, done chan int) {\r\n  requestSemaphore &lt;- 1 \/\/ Block until put in the semaphore queue\r\n  response, realURL, err := http.Get(url)\r\n  if err == nil {\r\n    bufferedReader := bufio.NewReader(response.Body)\r\n    err = searchAll(search, bufferedReader, url, out)\r\n    response.Body.Close()\r\n  }\r\n  if err != nil {\r\n    die(\"Could not read from \" + realURL + \":\" + err.String())\r\n  }\r\n  &lt;- requestSemaphore \/\/ Dequeue from the semaphore\r\n  done &lt;- 1 \/\/ Signal that function is done\r\n}\r\n\r\nfunc searchAll(search string, reader *bufio.Reader, httpRoot string, out chan string) (os.Error) {\r\n  var error os.Error = nil;\r\n  for {\r\n    var line, err = reader.ReadString('\\n')\r\n    if err != nil {\r\n      if err == os.EOF {\r\n        break\r\n      }\r\n      return error\r\n    }\r\n    var hrefs = rpmRegexp.AllMatchesStringIter(line, 0)\r\n    for href := range hrefs {\r\n      start := strings.Index(href, \"\\\"\") + 1\r\n      end := len(href) - 1\r\n      packageFile := href[start : end]\r\n      if strings.Index(packageFile, search) != -1 {\r\n        out &lt;- httpRoot + packageFile\r\n      }\r\n    }\r\n  }\r\n  return error\r\n}\r\n<\/pre>\n<h1>A quick overview<\/h1>\n<p>Go uses &#8220;channels&#8221; for communication among threads.<\/p>\n<ol>\n<li>The main thread creates two channels, one to receive search output and one to receive &#8220;done&#8221; responses when a child thread finishes.<\/li>\n<li>The main thread attaches the output channel to a simple function that prints to standard out.<\/li>\n<li>The main thread spawns one child thread per server, giving each the output and done channels.<\/li>\n<li>The main thread loops over the done channel once for each server, waiting until all requests are complete.<\/li>\n<li>Each child blocks immediately until space is available in a semaphore.<\/li>\n<li>The child performs the HTTP get, then searches the response for anything matching a regular expression.\u00a0 Any matches are signaled back to the output channel.<\/li>\n<li>The child opens a space in the semaphore and signals to the main thread that it is done.<\/li>\n<\/ol>\n<p>There isn&#8217;t much more to say.\u00a0 The <a href=\"http:\/\/golang.org\/\">Go web site<\/a> has <a href=\"http:\/\/golang.org\/doc\/go_tutorial.html\">great tutorials<\/a>, so I don&#8217;t yet feel the need to write one.<\/p>\n<p>By the way, if you&#8217;re searching for Go resources, be sure to search for &#8220;go language&#8221; and not &#8220;go&#8221;.\u00a0 But then, that&#8217;s probably how you found this page \ud83d\ude09<\/p>\n<div id=\"_mcePaste\" style=\"position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;\">package main<\/p>\n<p>import (<br \/>\n&#8220;bufio&#8221;<br \/>\n&#8220;fmt&#8221;<br \/>\n&#8220;http&#8221;<br \/>\n&#8220;os&#8221;<br \/>\n&#8220;regexp&#8221;<br \/>\n&#8220;strings&#8221;<br \/>\n)<\/p>\n<p>var servers = []string {<br \/>\n&#8220;http:\/\/apt.duncllc.com\/dist\/WS4.0\/RedHat\/RPMS\/&#8221;,<br \/>\n&#8220;http:\/\/apt.duncllc.com\/dist\/WS4.0\/RedHat\/RPMS.extras\/&#8221;,<br \/>\n&#8220;http:\/\/apt.duncllc.com\/dist\/WS4.0\/RedHat\/RPMS.java\/&#8221;}<\/p>\n<p>var maxOpenRequests int = 3<\/p>\n<p>var rpmRegexp = regexp.MustCompile(&#8220;href=\\&#8221;[^\\&#8221;]+\\&#8221;&#8221;)<\/p>\n<p>func main() {<br \/>\nsearch := getSearchTerm()<br \/>\nout := make(chan string)<br \/>\ndone := make(chan int)<br \/>\ngo printer(out)<br \/>\nfor _, url := range servers {<br \/>\ngo searchURL(search, url, out, done)<br \/>\n}<br \/>\nfor i := 0; i &lt; len(servers); i++ {<br \/>\n&lt;- done<br \/>\n}<br \/>\n}<\/p>\n<p>func getSearchTerm() string {<br \/>\nargs := os.Args<br \/>\nif len(args) != 2 {<br \/>\ndie(&#8220;Please enter one search term&#8221;)<br \/>\n}<br \/>\nreturn args[1]<br \/>\n}<\/p>\n<p>func die(message string) {<br \/>\nfmt.Println(message)<br \/>\nos.Exit(1)<br \/>\n}<\/p>\n<p>func printer(out chan string) {<br \/>\nfor {<br \/>\nfmt.Println(&lt;- out)<br \/>\n}<br \/>\n}<\/p>\n<p>var requestSemaphore = make(chan int, maxOpenRequests) \/\/ Integer chanel with a maximum queue size<\/p>\n<p>func searchURL(search string, url string, out chan string, done chan int) {<br \/>\nrequestSemaphore &lt;- 1 \/\/ Block until put in the semaphore queue<br \/>\nresponse, realURL, err := http.Get(url)<br \/>\nif err == nil {<br \/>\nbufferedReader := bufio.NewReader(response.Body)<br \/>\nerr = searchAll(search, bufferedReader, url, out)<br \/>\nresponse.Body.Close()<br \/>\n}<br \/>\nif err != nil {<br \/>\ndie(&#8220;Could not read from &#8221; + realURL + &#8220;:&#8221; + err.String())<br \/>\n}<br \/>\n&lt;- requestSemaphore \/\/ Dequeue from the semaphore<br \/>\ndone &lt;- 1 \/\/ Signal that function is done<br \/>\n}<\/p>\n<p>func searchAll(search string, reader *bufio.Reader, httpRoot string, out chan string) (os.Error) {<br \/>\nvar error os.Error = nil;<br \/>\nfor {<br \/>\nvar line, err = reader.ReadString(&#8216;\\n&#8217;)<br \/>\nif err != nil {<br \/>\nif err == os.EOF {<br \/>\nbreak<br \/>\n}<br \/>\nreturn error<br \/>\n}<br \/>\nvar hrefs = rpmRegexp.AllMatchesStringIter(line, 0)<br \/>\nfor href := range hrefs {<br \/>\nstart := strings.Index(href, &#8220;\\&#8221;&#8221;) + 1<br \/>\nend := len(href) &#8211; 1<br \/>\npackageFile := href[start : end]<br \/>\nif strings.Index(packageFile, search) != -1 {<br \/>\nout &lt;- httpRoot + packageFile<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\nreturn error<br \/>\n}<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>After hearing about Go and its love of parallelism, I wanted to give it a try. I couldn&#8217;t think of any small projects that needed to be threaded until recently.\u00a0 My employer uses an RPM system, but I&#8217;m using Ubuntu, which requires deb files.\u00a0 Searching through the RPMs in a browser is a pain, so [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[64],"tags":[42,43,15,5],"class_list":["post-221","post","type-post","status-publish","format-standard","hentry","category-go","tag-go","tag-go-language","tag-open","tag-programming"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1ql2n-3z","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/221","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=221"}],"version-history":[{"count":2,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/221\/revisions"}],"predecessor-version":[{"id":224,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/221\/revisions\/224"}],"wp:attachment":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=221"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=221"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=221"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}