Skip to content

Asynchronous Operations and Green Threads

Michael Wang edited this page Feb 1, 2025 · 1 revision

Overview of Green threads

HulaScript is still single threaded only. However, it supports multiple "simultaneous" green threads essentially via context-switching. Currently, there is no limit of the maximum number of green threads you can run in HulaScript.

The async call qualifier

Qualifying a function call by async starts a new green thread that runs that function. It return's an await token that can be awaited for the function's return result.

The Coloring Problem

A common problem in many other languages is how the async keyword is necessary in a function that uses await. As a result, many more functions are infectiously tagged as async simply by virtue of invoking another async function.

HulaScript solves the coloring problem via using async as a call-qualifier, not a function qualifier, on a call-by-call basis. Essentially, any HulaScript function can use the async keyword, thereby avoiding the coloring problem altogether.

Usage of async

For example, let's say we have a function test, which counts up to a number.

function test(a) no_capture do
	for i in irange(a) do
		print(a, ":", i)
	end
end

Let's say we want to invoke test multiple times, simultaneously, from 1...10.

for i in irange(10) do
	test async(11-i)
end

Notice that the async call qualifier was placed in the qualifiers section of the function call.

test async(11-i)

And the output of the function is disjoint and the counting happens simultaneously (ignoring a lock during printing)

11:0
10:0
11:1
9:0
10:1
11:2
8:0
9:1
10:2
11:3
... skipped a bunch of output ...
5:4
6:5
7:6
8:7
9:8
10:9
11:10

Note: The order of execution of green threads is NOT guaranteed.

The await keyword

Await in HulaScript behaves much like await in other programming languages. The await keyword is succeeded by a value that is an await token. The await keyword suspends the current green thread (the thread from which it is invoked) until the operation indicated by the await token is finished. The result of that operation, a value, is returned as well.

Usage of await

Consider the following example:

function test(a) no_capture do
        await sleep(5)
	return a
end
print(await test async(8))

There are two usages of await: await sleep(5) and await test async(8).

Let's examine await sleep(5). sleep(5) returns an await token that suspends the current thread until 5 seconds has elapsed. Calling sleep(5) alone wouldn't do anything - it would merely return an await token. The act of invoking the awaitkeyword, is what actually makes the thread sleep for 5 seconds. This value would evaluate tonil`.

Let's examine await test async(8). test async(8) returns an await token that suspends the current thread until test(8) has been fully evaluated. Because of the await sleep(5) in test, test(8) would sleep for 5 seconds during it's evaluation. Finally, when test(8) is finished and returns 8, await test async(8) will evaluate to 8.

A More Complex Example

Sleep sort is a classic demonstration of a programming languages time-sharing capabilities.

function sleepSort(toSort) no_capture do
	result = []

	helper = function(i) do
		await sleep(i)
		result.append(i)
	end

	await awaitAll variadic(for element in toSort do helper async(element) end)
	return result
end
sleepSort([3,2,1])

Note: Memory can be shared between green threads, and there is no restriction on the sharing/mutating of common memory.

Clone this wiki locally