Skip to content

foldcat/oasync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oasync

M:N multithreading for Odin. The end goal is to implement virtual threads that automatically and quickly parallelize tasks across several os threads.

notice

My exam is coming soon, expect fewer updates.

Active development will resume mid April.

usage

Note that this library is in PRE ALPHA STATE. It lacks essential features and may randomly cause segmented faults.

However, please test it out and provide feedbacks and bug reports!

In the examples below, we will be importing Oasync as so:

import oa "../oasync"

seting up the coordinator

// coordinators are the heart of oasync!
coord := oa.Coordinator{}

// the following examples will use this configuration:
// it will be using
// 4 (generic worker) + 2 (blocking worker) + 1 (main thread) = 7 threads

cfg := oa.Config {
  // amount of threads to use
  worker_count          = 4,

  // amount of threads to use for blocking
  blocking_worker_count = 2,

  // use the main thread as a worker 
  // this prevents the program from exiting immediately
  use_main_thread       = true,
}

fire off the first task

core :: proc() {
  // starting point!
}

// multiple coordinator can be spawned at the same time 
// as they are self contained 
// coordinators will not interract with each other
oa.init(&coord, cfg, oa.make_task(core))

running a virtual task

child :: proc() {
	fmt.println("hello from child!")
}

// DO NOT do this outside of threads managed by the coordinator
oa.go(child)

passing argument into another virtual task

// odin lacks the typesystem to express this in a type safe manner
// thus we pass data as a rawptr
child :: proc(raw_data: rawptr) {
	data := cast(^string)raw_data
	fmt.println(data^)
}

// we need to allocate on the heap as the stack gets destroyed upon 
// function finishing
// we allocate with temp allocator since the default allocator in a virtual task
// is swapped with an arena allocator
// the allocator resets itself upon task finishing execution
data := new_clone("pass data into childs threads~", context.temp_allocator)
oa.go(child2, data)

blocking tasks

// some actions blocks the worker, if too many blocking tasks are running, 
// it will paralyse the coordinator!
// please keep every task as short as possible to prevent this~

// if blocking is necessary,
blocking_child :: proc() {
	time.sleep(2 * time.Second)
	fmt.println("blocking child finishes!")
}

// use a blocking worker instead
for i in 1 ..= 4 {
	oa.gob(blocking_child)
}

// output: 
// after 2 seconds...
// blocking child finishes!
// blocking child finishes!
// after 2 more seconds...
// blocking child finishes!
// blocking child finishes!

// blocking workers are a seprate pool of workers, this will make sure 
// there are still rooms for non blocking tasks out there to run!


blocking_child_witharg :: proc(arg: rawptr) {}
// note that you can also pass in a rawptr: 
oa.gob(blocking_child_witharg, input)

// don't worry, they will still be used to perform non-blocking tasks 
// when there are no blocking tasks! 
// of course, this is slightly slower than generic workers, but they 
// are trying their best so don't judge~

unsafe dispatching

// sometimes you are in threads not managed by the coordinator,
// but you still want to spawn virtual tasks

// you can do this:
task :: proc() {}
// pass in the coordinator into the last argument!
unsafe_go(task, &coord)
unsafe_gob(task, &coord)

task_witharg :: proc(arg: rawptr) {}
unsafe_go(task, input, &coord)
unsafe_gob(task, input, &coord)

// this really isn't unsafe in the sense that it might cause crashes:
// this is just slower than dispatching normally...
// avoid this as much as possible!

// if these procedures are called before initializing the coordinator, 
// they will be ran when the coordinator gets initialized
// note that this might cause the procedure passed in oa.init 
// to not be run before anything does!

context system

// we inject a pointer to a struct named Ref_Carrier into context.user_ptr
// please do NOT modify it!

// if you want to use that field still, cast said pointer 
// into a Ref_Carrier and use the user_ptr field stored inside it 
// please do NOT touch the worker field!

// however, this is still not recommended as context persist 
// across workers and NOT singular virtual threads!
// same applies to ANYTHING related to the context system!
ref_carrier := cast(^Ref_Carrier)context.user_ptr
ref_carrier.user_ptr = ...

Releases

No releases published

Packages

No packages published

Languages