Check out wingo's fibers. I added support for fibers in guile-log and guile-log prolog. Fibers are an attempt to model multitasking, like in erlang, with cooperative scheduling (meaning that you need to enter a sleep now and then to allow other threads to run. Currently the interface is very simplistic. Here is the prolog interface
To enable this feature use the library
(logic guile-log guile-prolog fiber). To enable code that use the scheduler we can use
This will execute goal with a fiber scheduler enabled, when all fibers have finished this ideom will succeed, the current engine is used for variable bindings.
We can spawn a new fiber inside a
with_fiber goal e.g.
Goal in a new engine in a separate fiber. This engine will need to communicate via channels preferable (see below).
So we can spawn separate fibers. They will not schedule unless you specifically tell them to sleep, wait on io or a result over a channel. To do io in a way that let the io fiber sleep untill io finishes, one need to use the non blocking version of the guile io internals. Sleep and channels can be used as
sleep(TimeImMs). make_channel(Channel). put_message(Channel, Message). get_message(Channel, Message).
That's it. This is the basic building block to make a quite potent web server where you spawn threads in the same process. This is prefereable multitasking when the scale is not too large and the system is not CPU bound. The reason is of cause that global data is consistant and you may share global state without lock.
It is possible to instrument code to be executed in a fiber within time-slices making sure to inject a possible check of the time alive for the time slice. Then one need to add ideoms for protecting data consistancy. So either the user cooperatively, like it is now, add sleeps. Or we automatically add sleeps, but add data protecting ideoms (e.g. prevent sleep injection).
Another feature that will be added are that prolog variables can be protected by having each fiber make their own binding. So you can make sure that global state is locally modified and only at certain synchronization points the data is communicated back to the global state. This is an effective way to maintain global data between many cores. e.g. make sure to synchronize chunks of data and not just each single atom and hence amortize the cost of making all data the same on all cores. Now to be effective normale you bind a variable through setting the actual box and store an undo field onto a undo stack. This fails in multithreading where you want to branch the value depending on the thread. But guile-log supports a binding through a assoc as well, which is a very effective functional assoc structure with constant lookup characteristic. Now each variable stores a thread id, if you want to bind a variable to a variable with a missmatching thread you update the assoc in stead of the actual thread. Guile log has an implementation of vhashes, that are thread safe if you do the right dance (guile's vhashes arn'et) and we base the assoc binding on these.
Finally an example,
?- make_channel(Ch), with_fibers( ( spawn_fiber(put_message(Ch,1)), get_message(Ch,X), write(X) )). 1 Ch = #<<channel> queue-size: 1 state: #<atomic-box 75a52f0 value: ()>>, X = 1.
That's all for now, Happy Hacking