{"id":15632778,"url":"https://github.com/zkat/chanl","last_synced_at":"2026-01-27T23:47:11.650Z","repository":{"id":664490,"uuid":"307558","full_name":"zkat/chanl","owner":"zkat","description":"Portable channel-based concurrency for Common Lisp","archived":false,"fork":false,"pushed_at":"2025-01-30T16:04:23.000Z","size":592,"stargazers_count":171,"open_issues_count":8,"forks_count":18,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-03-29T19:22:34.529Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zkat.png","metadata":{"files":{"readme":"README.mkdn","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2009-09-15T08:53:23.000Z","updated_at":"2025-01-30T16:04:30.000Z","dependencies_parsed_at":"2024-08-22T06:32:43.997Z","dependency_job_id":"e5dea84a-4136-407e-96b8-ad2cc0df75b6","html_url":"https://github.com/zkat/chanl","commit_stats":{"total_commits":490,"total_committers":9,"mean_commits":54.44444444444444,"dds":"0.34693877551020413","last_synced_commit":"3e768618a4ba88084bce54c201d2b86e84510b2c"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/zkat/chanl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkat%2Fchanl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkat%2Fchanl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkat%2Fchanl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkat%2Fchanl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zkat","download_url":"https://codeload.github.com/zkat/chanl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkat%2Fchanl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28827902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T23:29:49.665Z","status":"ssl_error","status_checked_at":"2026-01-27T23:25:58.379Z","response_time":168,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-10-03T10:45:17.221Z","updated_at":"2026-01-27T23:47:11.631Z","avatar_url":"https://github.com/zkat.png","language":"Common Lisp","readme":"What is ChanL?\n--------------\n\n\"Wh-what you have to understand is that-that ChanL is not a big blob\nof state. When you have a lot of global state with lots of threads,\nyou need to, you need to lock it down. It's like a truck, and if too\nmany people try to use that truck, you, you get problems.  ChanL is\nnot a big truck. It's a series--it's a series of *tubes*.\"\n\n  - Sen. Ted Stevens\n\nIn a nutshell, you create various threads sequentially executing tasks\nyou need done, and use channel objects to communicate and synchronize\nthe state of these threads.\n\nYou can read more about what that means here:\n\n- http://swtch.com/~rsc/thread/\n\n- http://www.usingcsp.com/cspbook.pdf\n\n\nLoading ChanL\n-------------\n\nChanL uses asdf for compiling/loading, so in order to load it, you\nmust first make chanl.asd visible to your lisp, then simply\n\n       (asdf:load-system 'chanl)\n\nThe included examples can be loaded by doing\n\n       (asdf:load-system 'chanl.examples)\n\nat the REPL after the main .asd has been loaded.\n\n\nCompatibility\n-------------\n\nChanL uses a subset of Bordeaux-threads for all its operations. All\nother code is written in ANSI CL, so any lisp with a BT-compatible\nthread library _should_ be able to use this library; however, due to\ninconsistencies, ChanL is likely to fail on compilers that have not\nbeen updated recently, so **please report portability problems!**.\n\nChanL is mainly developed on Clozure CL and SBCL, although it's been\ncasually tested on other lisps.\n\nYou can run the test suite to see how well it works on yours:\n\n       (asdf:test-system 'chanl)\n\nNote that the test-suite depends on 5AM, which can be found at\nhttp://github.com/sionescu/fiveam\n\n\nChannel API\n-----------\n\n*[generic function]* `make-instance class \u0026rest initargs \u0026key \u0026allow-other-keys`\n\n*[method]* `make-instance (class channel) \u0026rest initargs`\n\n  Returns a new channel object.\n\n\n*[method]* `make-instance (class unbounded-channel) \u0026rest initargs`\n\n  Returns a new buffered channel with a FIFO buffer with no maximum length.\n\n\n*[method]* `make-instance (class bounded-channel) \u0026key (size 1) \u0026rest initargs`\n\n  Creates a new buffered channel object with a limited buffer\n  size. The buffer has a maximum size of SIZE. Bounded channel buffers\n  are FIFO. When the buffer is full, SEND will block until something\n  is RECVd from the channel.\n\n  SIZE must be positive and less than +maximum-buffer-size+, and\n  defaults to 1.\n\n\n*[method]* `make-instance (class stack-channel) \u0026rest initargs`\n\n  Returns a new buffered channel with a LIFO buffer with no maximum length.\n\n\n*[constant]* `+maximum-buffer-size+`\n\n  This constant has an implementation-dependant value, fixed when\n  ChanL is loaded. It is the exclusive upper bound to the size of a\n  bounded-channel's buffer.\n\n  Note that this value might be further limited by memory constraints.\n\n\n*[generic function]* `send channel value \u0026key`\n\n*[method]* `send (channel channel) value \u0026key (blockp t)`\n\n  Tries to send VALUE into CHANNEL. If the channel is unbufferd or\n  buffered but full, this operation will block until RECV is called on\n  the channel. Returns the channel that the value was sent into.  If\n  blockp is NIL, NIL is returned immediately instead of a channel if\n  attempting to send would block.\n\n\n*[method]* `send (channels sequence) value \u0026key (blockp t)`\n\n  SEND may be used on a sequence of channels. SEND will linearly attempt to send VALUE into one of\n  the channels in the sequence. It will return immediately as soon as it is able to send VALUE into\n  one channel. If BLOCKP is T (default), SEND will continue to block until one operation succeeds,\n  otherwise, it will return NIL when the sequence has been exhausted.\n\n\n*[generic function]* `recv channel \u0026key`\n\n*[method]* `recv (channel channel) \u0026key (blockp t)`\n\n  Tries to receive a value from CHANNEL. If the channel is unbuffered,\n  or buffered but empty, this operation will block until SEND is\n  called on the channel. Returns two values: 1. The value received\n  through the channel, and 2. The channel the value was sent into. If\n  BLOCKP is nil, this operation will not block, and will return\n  `(values NIL NIL)` if attempting it would block.\n\n\n*[method]* `recv (channel sequence) \u0026key (blockp t)`\n\n  RECV may be used on a sequence of channels. RECV will linearly\n  attempt to receive a value from one of the channels in teh\n  sequence. It will return immediately as soon as one channel has a\n  value available. As with the method for CHANNEL, this will return\n  the value received, as well as the channel the value was received\n  from. If BLOCKP is NIL, and operating on all channels in sequence\n  would block, `(values NIL NIL)` is returned instead of blocking.\n\n\n*[macro]* `select \u0026body clauses*`\n  Non-deterministically select a non-blocking clause to execute.\n\n  The syntax is:\n\n     select clause*   -\u003e  result(s)\n     clause ::= (op form*)\n     op ::=   (recv c \u0026optional variable channel-var)\n            | (send c value \u0026optional channel-var)\n            | else | otherwise | t\n\n     c ::= an evaluated form representing either one single channel,\n     or a sequence containing multiple ordered channels.\n\n     variable ::= a symbol, bound within form* to RECV's return value.\n\n     value ::= an form evaluated for the value to send to the channel.\n\n     channel-var ::= A symbol bound to the active SEND/RECV channel.\n\n     result(s) ::= the values returned by first clause to act up.\n\n  SELECT will first attempt to find a clause with a non-blocking op,\n  and execute it.  Selecting a clause to execute is atomic, but\n  execution of the clause's body after the SEND/RECV clause executes\n  is NOT atomic.  If all channel clauses would block, and no else\n  clause is provided, SELECT will thrash-idle (an undesirable state!)\n  until one of the clauses is available for execution.\n\n    SELECT's non-determinism is, in fact, very non-deterministic.\n  Clauses are chosen at random, not in the order they are written.\n  It's worth noting that SEND/RECV, when used on sequences, are still\n  linear in the way they go through the sequence -- the random\n  selection is reserved for individual SELECT clauses.\n\n    Please note that currently, the form for the channel in the RECV\n  and SEND clauses and for the value in the SEND clause might be\n  evaluated multiple times in an unspecified order. It is thus\n  undesirable to place forms with side-effects in these places. This\n  is a bug and will be fixed in a future version of ChanL.\n\n\nThread API\n----------\n\n*[special variable]* `*default-special-bindings*`\n\n  alist of bindings new threads should have; its template is:\n  '((*var1* 'value) (*var2* 'value2)).\n\n\n*[function]* `pcall function \u0026key initial-bindings name`\n\n    PCALL (mnemonic Parallel Call) calls FUNCTION in some thread.\n  FUNCTION must be a function of zero arguments.  INITIAL-BINDINGS, if\n  provided, are bound around the execution, and should follow the same\n  template as *default-special-bindings*, also the defaults if no\n  keyword argument is given.\n\n    PCALL returns a task object representing this task.  This object is\n  intended for interactive use in reflection and debugging, and\n  contains some of metadata about the execution.  The NAME argument\n  can be used to initialize the name slot of the task object.\n\n\n*[macro]* `pexec (\u0026key (initial-bindings *default-special-bindings*)) \u0026body body`\n\n  Executes BODY in parallel. INITIAL-BINDINGS, if provided, should be\n  an alist representing dynamic variable bindings that BODY is to be\n  executed with, as if with *default-special-bindings*. NAME can be\n  used to initialize the name slot of the returned task object. PEXEC\n  also returns a task.\n\n\nThread Introspection\n--------------------\n\n  ChanL includes portable support for lisp threads through\nbordeaux-threads, and adds some sugar on top, such as a built-in\nthread pool. None of the thread functions here should be used in user\ncode, since they are meant exclusively for development purposes. Most\nthread-related matters are automatically handled by the thread pool\nalready. In fact, not a single one of these should be expected to work\nproperly when you use them, so do so at your own risk.\n\n\n*[class]* `task`\n\n    Tasks represent the bits of work carried out by threads.  Task\n  objects should be treated as read- only debugging aids.  The\n  functions TASK-NAME, TASK-THREAD, and TASK-STATUS return metadata\n  about the task.  Since this is an experimental feature, its API is\n  likely to change -- the current behavior can be checked in\n  src/threads.lisp.\n\n\n*[function]* `current-thread`\n\n  Returns the current thread\n\n\n*[function]* `thread-alive-p thread`\n\n  T if THREAD is still alive\n\n\n*[function]* `threadp maybe-thread`\n\n  T if maybe-thread is, in fact, a thread.\n\n\n*[function]* `thread-name thread`\n\n  Returns the name of the thread.\n\n\n*[function]* `kill thread`\n\n  Kills thread dead.\n\n\n*[function]* `all-threads`\n\n  Returns a list of all threads currently running in the lisp image.\n\n\n*[function]* `pooled-threads`\n\n  Returns a list of all threads currently managed by ChanL's thread pool.\n\n\n*[function]* `pooled-tasks`\n\n  Returns a list of all tasks pending or live in ChanL's thread pool.\n\n\n*[symbol-macro]* `%thread-pool-soft-limit`\n\n  This is a SETFable place. It may be used to inspect and change the\n  soft limit for how many threads the thread pool keeps around. Note\n  that the total number of threads will exceed this limit if threads\n  continue to be spawned while others are still running. This only\n  refers to the number of threads kept alive for later use.  The\n  default value is 1000.\n\n\nWriting your own channels\n-------------------------\n\nCurrently, ChanL provides a very early API for writing your own channels easily.\n\n*[class]* `abstract-channel`\n\n  This class is the ancestral superclass of all channels. CHANNELP\n  returns T for any instances of this class or its subclasses.\n\n  Direct subclasses of abstract-channel will have to define their own\n  SEND/RECV methods, which should meet the API requirements in order\n  to be compatible with ChanL.\n\n\n*[class]* `channel`\n\n    This is the main unbuffered class used in ChanL. Unless you wish to\n  write a new synchronization algorithm for your custom channels, you\n  should subclass CHANNEL, since you then get to reuse ChanL's\n  build-in algorithm (which relies on locks and condition variables).\n\n\n    Subclasses of the CHANNEL class are able to extend behavior in a\n  fairly flexible way by simply writing methods for the following 4\n  generic functions:\n\n*[generic function]* `send-blocks-p channel`\n\n    This function returns, as a generalized boolean, whether SEND\n  should block on this channel.\n\n\n*[generic function]* `recv-blocks-p channel`\n\n  Like send-blocks-p, but for RECV.\n\n\n    Please note that the consequences of calling send-blocks-p and\n  recv-blocks-p in user code are *undefined* -- these functions are\n  called from specific points within the carefully instrumented\n  algorithms of the SEND and RECV methods specialized on the CHANNEL\n  class.\n\n\n*[generic function]* `channel-insert-value channel value`\n\n    Methods on this function define what actions are taken to insert a\n  value into a channel. Methods should be specialized only on the\n  first argument. This function's return values are ignored.\n\n\n*[generic function]* `channel-grab-value channel`\n\n    Methods on this function define how to retrieve a value from a\n  channel. This function must return at least one value, the object\n  retrieved from the channel, which will then be returned by RECV.\n\n\n    Additionally, ChanL uses and exports a number of abstract and\n  concrete classes to implement its buffered channels:\n\n\n*[abstract class]* `buffered-channel`\n\n  Abstract class for channels using various buffering styles.\n\n\n*[abstract class]* `queue-channel`\n\n  Abstract class for channels whose buffer works like a queue.\n\n\n*[class]* `bounded-channel`\n\n  Class used by ChanL's bounded channels (queue channels that block\n  when the queue reaches a certain length).\n\n\n*[class]* `unbounded-channel`\n\n  Class used by ChanL's unbounded channels, which are queues of\n  unlimited length (SEND never blocks)\n\n\n*[class]* `stack-channel`\n\n  Class used by ChanL's stack channels. These channels' buffers are\n  unbounded LIFO stack structures.\n\n\nExamples\n--------\n\nCreate a channel:\n\n     (defvar *c* (make-instance 'channel))\n\nCreate a buffered channel with a buffer size of 5. Buffered channels\ndo not block on send until their buffer is full, or on recv until\ntheir buffer is empty.\n\n     (defvar *c* (make-instance 'bounded-channel :size 5))\n\nRead a value from a channel (blocks if channel is empty)\n\n     (recv *c*)\n\nWrite a value to a channel (blocks if channel is full, always blocks\non unbuffered channels)\n\n     (send *c* 99)\n\nWait for any of a number of things to occur:\n\n     (select\n       ((recv c d)\n        (format t \"got ~a from c~%\" d))\n       ((send e val)\n        (print \"sent val on e~%\"))\n       ((recv *lots-of-channels* value channel)\n        (format t \"Got ~A from ~C~%\" value channel))\n       (otherwise\n        (print \"would have blocked~%\")))\n\nCreate a new thread continually reading values and printing them:\n\n     (pexec ()\n       (loop (format t \"~a~%\" (recv *c*))))\n\nCreate a new thread that runs a function:\n\n     (pcall #'my-function)\n\n  Please refer to the examples/ directory for examples of how ChanL\ncan be used, including a parallel prime sieve algorithm translated\nfrom Newsqueak and an implementation of future-based concurrency.\n","funding_links":[],"categories":["Interfaces to other package managers"],"sub_categories":["Third-party APIs"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzkat%2Fchanl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzkat%2Fchanl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzkat%2Fchanl/lists"}