How to implement parallel-or?

There are several good options.

From a download site like https://pre-release.racket-lang.org, you can have the installer perform an in-place installation (or select a distribution packaged as a tarball), putting the whole installation in a directory like ~/racket-8.18.0.19/, so that the racket executable is at ~/racket-8.18.0.19/bin/racket. That works especially well for a one-off short-term use like this, given that the Racket 9.0 release should be on Friday, give or take.

The raco cross tool can serve as a more automated way of doing basically the same thing, even if you don't need its additional features for cross-compilation and such.

You can also install multiple Racket versions on a machine long-term, similar to the way /lib/qt5/ and /lib/qt6/ both exist on my laptop. Details will vary depending on your system. This is less likely to be useful for Racket, though, because Racket's strong emphasis on compatibility means you should almost never have to change your code to upgrade to the latest Racket release.

You can simplify somewhat (see below), but you need to substitute kill-thread for one of the custodian-shutdown-all calls, because, if one thread terminates with a non-false value, you surely want to terminate the other thread.

Custodians can manage more general resource-cleanup issues. For example, imagine you wanted to look up the weather in Chicago, and wanted to use parallel-or to try both the National Weather Service API and Open-Meteo, returning the result from whichever supplies it fastest. Using thread-kill would stop the thread, freeing CPU resources, but it could potentially leave open file descriptors or other OS-level resources implementing an HTTP connection. More generally, a given thread could have launched other threads to perform its work. Using custodians can reliably reclaim all potentially-used resources.

(define (do-parallel-or a-thunk b-thunk)
  (define (spawn thunk)
    (thread #:pool 'own #:keep 'results thunk))
  (define a-thd
    (spawn a-thunk))
  (define b-thd
    (spawn b-thunk))
  (define ready-thd
    (sync a-thd b-thd))
  (define other-thd
    (if (equal? ready-thd a-thd)
        b-thd
        a-thd))
  (cond
    [(thread-wait ready-thd)
     => (λ (rslt)
          (kill-thread other-thd)
          rslt)]
    [else
     (thread-wait other-thd)]))

Outside of an exercise, these find-a-string problems are very well studied, so you probably would want to make sure you're implementing one of the current state-of-the-art algorithms (though I couldn't say off-hand what you want). But your results from adding parallelism, whether to an optimal algorithm or not, might be useful to help evaluate the still-quite-new implementation of parallel threads:

1 Like