{"id":18879159,"url":"https://github.com/jackychiu/bounded","last_synced_at":"2025-04-14T19:12:38.533Z","repository":{"id":91227757,"uuid":"164373018","full_name":"JackyChiu/bounded","owner":"JackyChiu","description":"Thin concurrency wrapper that provides bounded goroutine management","archived":false,"fork":false,"pushed_at":"2019-12-01T00:15:06.000Z","size":26,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T19:12:33.911Z","etag":null,"topics":["concurrency","error-handling","go","goroutine","lazyloading","pipeline"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JackyChiu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-07T03:25:47.000Z","updated_at":"2019-12-01T00:15:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"8eea317a-ce1d-4613-ba2a-e9f674bd514a","html_url":"https://github.com/JackyChiu/bounded","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JackyChiu%2Fbounded","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JackyChiu%2Fbounded/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JackyChiu%2Fbounded/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JackyChiu%2Fbounded/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JackyChiu","download_url":"https://codeload.github.com/JackyChiu/bounded/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248943460,"owners_count":21186958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","error-handling","go","goroutine","lazyloading","pipeline"],"created_at":"2024-11-08T06:33:51.409Z","updated_at":"2025-04-14T19:12:38.526Z","avatar_url":"https://github.com/JackyChiu.png","language":"Go","readme":"# `bounded` [![](https://circleci.com/gh/JackyChiu/bounded.svg?style=svg)](https://circleci.com/gh/JackyChiu/bounded) [![Documentation](https://godoc.org/github.com/JackyChiu/bounded?status.svg)](https://godoc.org/github.com/JackyChiu/bounded/)\n\n## Bounded Goroutine Management\n`bounded.Pool` is a bounded goroutine manager. Pool provides:\n- Ensures goroutines spawned to be within the limit\n- Lazily spawns new goroutines when ones in the pool are busy, up to the limit\n- Captures the first non-nil `error` in the pool\n- Notifies other goroutines through the cancellation the returned context\n- Ability to wait for all goroutines to complete\n- Insignificant to no overhead compared to hand rolled worker pools\n\n```bash\ngo get github.com/JackyChiu/bounded\n```\n\n## Example\n```go\npool, ctx := bounded.NewPool(context.Background(), 20)\n\nfor {\n  select {\n    case message := \u003c-stream:\n      pool.Go(func () error {\n        // process message\n      })\n    case \u003c-ctx.Done():\n      // continue and check pool for error\n  }\n}\n\nif err := pool.Wait(); err != nil {\n  // handle error\n}\n```\n\nFor a more complete example checkout the\n[examples directory](https://github.com/JackyChiu/bounded/blob/master/examples/bounded/md5_all.go).\n\n## Why\nGo encourages programming with concurrent design, allowing us to execute\nindependent tasks with goroutines.  Much programs end up having boundless\nconcurrency and as a result they comes with producing a significant amount of\noverhead.\n\nThis package is a attempt at providing an thin API (along with synchronization/\nerror capturing built-in) to allow developers to continue programming with the\nsame mental model without concern for the overhead.\n\nThe API and error capturing was inspired by the [errgroup](https://godoc.org/golang.org/x/sync/errgroup) package.\n\n## Synchronization And Error Capture\nPool provides simple synchronization and error capturing abilities. It provides\nan API to wait for all tasks in the pool to complete and exit with `Wait()`. If\nan error occurs in the pool, it's capture and the goroutines are notified via\n`context.Context.Done()`. The main goroutine can also use this to tell if it\nshould stop producing work to the pool. The first error captured is returned.\n\n## Lazy\nPool lazily spawns workers in the pool as tasks are queued up. Tasks are\nfavored to be completed by an existing worker. If all workers are busy then\nit will spawn a new worker and enqueue the task again. This behaviour is\nongoing until the size of the pool has reached it's limit.\n\n```go\npool, ctx := bounded.NewPool(context.Background(), HUGE_NUMBER)\n\nfor message := range stream {\n  pool.Go(func() error {\n    // process message\n  })\n}\n\n// Pool will reuse goroutines. New goroutines are spawned when the workers\n// are busy, up to the pool limit.\n```\n\n## Gotchas\n\n### Deadlocks\nCalls to `Pool.Go` will block when a worker isn't available. This is a issue\nwhen you're designing a producer/consumer type system where you want to spawned\nworkers to produce results and consume the results them in the same goroutine.\n\n```go\nresults := make(chan results)\nfor message := range stream {\n  // This can block and cause a deadlock becauase nothing is able to consume\n  // from the results channel.\n  pool.Go(func() error {\n    // process message to sent to results channel\n  })\n}\ngo func() {\n  pool.Wait()\n  close(results)\n}\n\nfor r := range results {\n  // process results\n}\n```\n\nInstead consider have a separate goroutine to consume the messages and spawn\nthe workers from there. This will also allow you to have a goroutine\nresponsible for closing the `results` channel.\n\n```go\nresults := make(chan results)\ngo func() {\n  defer close(results)\n  for message := range stream {\n    pool.Go(func() error {\n      // process message to send to results channel\n    })\n  }\n  pool.Wait()\n}()\n\nfor r := range results {\n  // process results\n}\n\nif err := pool.Wait(); err != nil {\n  // handle error\n}\n```\n\n### Performance\n\nIn the producer/consumer model shown above, there is only one goroutine\nconsuming messages from the `messages` channel. Compared to possible hand roll\nsolutions that have multiple goroutines reading from the channel. This applies\nback pressure to the producer at the cost of the producer being blocked.\nTo increase performance of the producer give the `messages` channel a buffer so\nthat it isn't as blocked as often.\n\n```go\npool, ctx := bounded.NewPool(context.Background(), 20)\n\nmessages := make(chan message, 10)\npool.Go(func() error {\n  defer close(messages)\n  return produceMessages(messages)\n})\n\n// Consume messages\n\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjackychiu%2Fbounded","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjackychiu%2Fbounded","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjackychiu%2Fbounded/lists"}