Docs/Concurrency

Threads

Run work on several CPU cores.

On this page

valk.thread runs work on other OS threads, so CPU-heavy work can use several cores. For work that mostly waits, coroutines are lighter.

Start a thread#

Valk
use valk.thread

fn main() {
    let worker = thread.Thread[int].start(fn() int {
        let sum = 0
        each 0..1000 as i : sum += i
        return sum
    }) ! panic("Could not start a thread")

    println(worker.await()) // 499500
}
  • Thread[T].start runs a closure that returns a T; await() waits and returns the result.
  • thread.start(fn() { ... }) is the same for a closure without a result.
  • Starting a thread can fail with InitError.

What a thread closure can capture#

A closure that runs on another thread may capture numbers, bools and atomic values. Make an object or string atomic before capturing it; this copies it once, and the copy is read-only:

Valk
use valk.thread

class Job {
    name: String
    size: int
}

fn main() {
    let job: atomic Job = Job { name: "resize", size: 3 }
    let factor = 10

    let worker = thread.Thread[int].start(fn() int {
        return job.size * factor
    }) ! panic("Could not start a thread")

    println(worker.await()) // 30
}

To change data from several threads, use a Lock[T] or another type from Sharing data between threads.

Run many workers#

ThreadGroup[T] starts workers and returns their results in the order they finish:

Valk
use valk.thread

fn main() {
    let group = thread.ThreadGroup[int].new()
    each Array[int]{ 1, 2, 3 } as number {
        group.start(fn() int { return number * number }) ! panic("Could not start a worker")
    }
    let total = 0
    while group.has_pending() {
        let index, result = group.await_next() ! break
        total += result
    }
    println(total) // 14
}

await_next returns the start index of the finished worker and its result. Use ThreadGroup[void] for workers without a result.

Split a loop over all cores#

thread.parallel_for(count, handler) calls the handler for every index below count, spread over one thread per CPU, and returns when all calls are done:

Valk
use valk.thread

fn main() {
    let squares: atomic Array[uint] = Array[uint]{ 0 x 100 }
    thread.parallel_for(squares.length, fn(i: uint) {
        squares[i] = i * i
    })
    println(squares[9]) // 81
}

Elements of an atomic array of numbers can be assigned from any thread. A third argument sets the number of threads.

Reuse threads for short tasks#

thread.task queues a closure on a small pool of reused threads, which avoids starting a new thread for each small job:

Valk
use valk.thread

fn main() {
    let results: atomic Array[uint] = Array[uint]{ 0 x 4 }
    let tasks = Array[thread.Task]{}
    each 0..4 as i {
        let index: uint = i
        tasks.append(thread.task(fn() { results[index] = index * 10 }) ! panic("Could not queue a task"))
    }
    each tasks as task : task.await()
    println(results[3]) // 30
}

Sleep#

Function Pauses
time.sleep_ms(ms) only the current coroutine; others on the thread keep running
thread.suspend_ms(ms) the whole thread, with all its coroutines

Prefer time.sleep_ms. To pass values between threads while they run, use channels.