From 498f59a937557910ac6a1fd9ddc6055dab06feaf Mon Sep 17 00:00:00 2001 From: jacopodl Date: Sat, 9 Sep 2023 15:25:52 +0200 Subject: [PATCH] feat: asynchronous programming --- docs/Async programming/README.md | 18 ++++++ docs/Async programming/async.md | 48 ++++++++++++++++ docs/Async programming/chan.md | 88 +++++++++++++++++++++++++++++ docs/Async programming/spawn.md | 35 ++++++++++++ docs/Async programming/syncblock.md | 61 ++++++++++++++++++++ docs/Get started/README.md | 9 +++ 6 files changed, 259 insertions(+) create mode 100644 docs/Async programming/README.md create mode 100644 docs/Async programming/async.md create mode 100644 docs/Async programming/chan.md create mode 100644 docs/Async programming/spawn.md create mode 100644 docs/Async programming/syncblock.md create mode 100644 docs/Get started/README.md diff --git a/docs/Async programming/README.md b/docs/Async programming/README.md new file mode 100644 index 0000000..9b4241c --- /dev/null +++ b/docs/Async programming/README.md @@ -0,0 +1,18 @@ +--- +title: Asynchronous programming +sidebar_position: 3 +--- + +Argon provides robust support for parallel programming, making it well-suited for concurrent and multi-threaded applications. + +It offers several features that enable developers to harness the power of parallelism effectively, including `chan`, asynchronous functions, and the ability to launch lightweight threads using the `spawn` keyword. + +Before delving into individual functionalities, it's essential to understand how the execution of a program is internally managed. + +## Argon execution model +During the bootstrap process, Argon requests the operating system to create N threads. The number of threads created at startup depends on the available CPU cores on the machine (although this setting can be adjusted). +These threads are initially paused and organized within a thread pool. + +When a program is loaded into memory, an object called a **Fiber** is created. This fiber is then associated with the first available thread, allowing it to be executed. In cases where execution cannot proceed immediately, such as during I/O operations, the currently executing fiber is suspended, and another available fiber is loaded in its place. + +This approach allows Argon to efficiently manage concurrent and parallel execution by seamlessly switching between fibers as needed, optimizing resource utilization and responsiveness in various scenarios. diff --git a/docs/Async programming/async.md b/docs/Async programming/async.md new file mode 100644 index 0000000..768830b --- /dev/null +++ b/docs/Async programming/async.md @@ -0,0 +1,48 @@ +--- +title: Async functions +sidebar_position: 1 +--- + +One of the simplest ways to execute code in parallel is to promote a function to an asynchronous function. This operation can be performed during the function's definition using the `async` keyword. + +An asynchronous function, when invoked, will immediately return a `Future` object. + +```javascript +import "http" + +async func async_get(url) { + return http.get(url) +} + +res := async_get("https://www.arlang.io/") # Output: Future object +``` + +## Future +An object `Future` is very similar to the concept of a Promise in many programming languages. It represents the result of an asynchronous action that may not be completed at the time of its creation. This mechanism provides an efficient way to handle operations that take time, such as network calls or I/O operations. + +By itself, a `Future` object isn't very useful, especially as it doesn't represent the actual result of the invoked function. To obtain the result, it's necessary to use the `await` keyword: + +```javascript +res = await res +``` + +The behavior of this statement directly depends on the completion status of the invoked asynchronous function and can behave in two ways: + +1. If the asynchronous function is not yet finished, the `fiber` that called `await` is **suspended until the asynchronous function is completed**. Afterward, it will return a Result object. + +2. If the asynchronous function has already completed, `await` **immediately returns** a Result object. + +```javascript +import "io" + +res = await res + +if !res { + panic res.err() +} + +res.ok().read(-1) |> io.print +``` + +Once you obtain the `Result` object, you can check if an error occurred by using `res.err()`. +In the case of success, you can retrieve the actual return value of the asynchronous function using `res.ok()`. \ No newline at end of file diff --git a/docs/Async programming/chan.md b/docs/Async programming/chan.md new file mode 100644 index 0000000..cb3c914 --- /dev/null +++ b/docs/Async programming/chan.md @@ -0,0 +1,88 @@ +--- +title: Channels +sidebar_position: 3 +--- + +How can we enable two or more spawned functions to **communicate with each other?** When **different parts** of a program run in parallel, it's almost certainly necessary to establish a means for these parts to **communicate, exchange messages, or coordinate** their actions effectively. + +One of the primary mechanisms provided by Argon for achieving this is the use of `Chan`. Channels are synchronization primitives that facilitate communication between multiple **fibers**. + +**Depending on how they are constructed**, they can act as straightforward **synchronization mechanisms** or as **full-fledged message queues**. + +Channels **automatically suspend** the execution of the **fiber** if writing to or reading from the channel is not currently possible. This relieves the developer from the burden of managing low-level synchronization and allows them to focus solely on their code. In this way, channels simplify the task of enabling communication and coordination among concurrently executing parts of a program. + +## Channel creation +To create a `Chan` object in Argon, you simply need to invoke its constructor: + +```javascript +chan := Chan() + +# Messages queue +chan := Chan(backlog=100) +``` + +In the example above, a channel with the capability to send and receive a single message has been created (useful for synchronization operations). Then, in the second example, the optional associative parameter of the constructor is used to create a channel capable of functioning as a message queue. + +This flexibility allows you to tailor the behavior of the channel to your specific needs, whether it's for simple synchronization or message passing. + +## Chan operations +There are two primary operations that can be performed on a channel: reading and writing. + +To facilitate these operations, Argon provides two special operators: +- write arrow `->` +- read arrow `<-` + +Let's see how to use them in a real context: + +```javascript +import "io" + +# Create a channel 'queue' with a backlog of 10 messages +queue := Chan(backlog=10) + +# Create a rendezvous channel +rendezvous := Chan() + +# Spawn a new fiber +spawn () => { + # Receive data from the 'queue' channel + data := <- queue + + loop data != @stop { + data |> io.print + + # Receive the next 'data' from the 'queue' channel + data = <- queue + } + + # Send '@exit' to the 'rendezvous' channel when done + @exit -> rendezvous +}() + +fd := io.open("file.txt") + +lines := fd.readline(-1) + +# Loop through each line +loop lines { + # Send 'lines' to the 'queue' channel + lines -> queue + + # Read the next line + lines = fd.readline(-1) +} + +# Send '@stop' to the 'queue' channel to signal the end of data +@stop -> queue + +"Waiting printer to finish..." |> io.print + +# Wait for the fiber to finish by reading from 'rendezvous' +<- rendezvous + +"Exit!" |> io.print +``` + +This example demonstrates a scenario where data is read from a file, sent to a queue for processing by a separate fiber, and then the program waits for the fiber to finish processing before exiting. + +The `@stop` and `@exit` atoms are used for synchronization and control flow between threads. \ No newline at end of file diff --git a/docs/Async programming/spawn.md b/docs/Async programming/spawn.md new file mode 100644 index 0000000..b035732 --- /dev/null +++ b/docs/Async programming/spawn.md @@ -0,0 +1,35 @@ +--- +title: The spawn keyword +sidebar_position: 2 +--- + +In the previous chapter, we explored asynchronous functions; functions that terminate when they generate their result. + +However, what if we want to concurrently progress with one or more operations without waiting for their results? + +A common example is a web server that listens for incoming connections on a port and handles each connection in a separate process without interrupting the listening for new connections. + +In such scenarios, we turn to the `spawn` keyword. The `spawn` keyword is used to initiate a new **fiber** (lightweight thread) that will execute a specific function concurrently with the calling process. + +A process launched with `spawn` can run its code independently, without affecting the main process or other concurrently spawned processes. + +```javascript +import "io" +import "socket" + +sock := socket.Socket(socket.AF_INET, socket.SOCK_STREAM, 0) + +sock.bind(("127.0.0.1", 2427)) + +sock.listen(5) + +loop { + conn := sock.accept() + + spawn () => { + conn.read(-1) |> io.print + + conn.write("recv!") + }() +} +``` \ No newline at end of file diff --git a/docs/Async programming/syncblock.md b/docs/Async programming/syncblock.md new file mode 100644 index 0000000..cd795e9 --- /dev/null +++ b/docs/Async programming/syncblock.md @@ -0,0 +1,61 @@ +--- +title: Synchronization blocks +sidebar_position: 4 +--- + +Sometimes, it's necessary to **protect** certain sections of your code **from concurrent access**. To address this need, Argon provides a construct called `sync`. Internally, this construct utilizes the concept of a **monitor** to automatically manage concurrent code sections. + +Before diving into the usage of the `sync` construct, let's briefly explain what a **monitor** is: + +A monitor is a synchronization construct that provides a way to protect shared resources or data in a multithreaded or concurrent program. + +It ensures that only one thread can access the protected code or data at a time, preventing race conditions and maintaining data integrity by offering a high-level and structured approach to synchronization, simplifying the task of coordinating access to critical sections of code. + +## How use `sync` block +A synchronization block is composed of the `sync` keyword followed by a variable reference: + +```javascript +var myvar = 12 + +sync myvar { + +} +``` + +In this example, we see how `myvar` is used as a synchronization point for the code block inside sync. + +> NB: If the value of the `myvar` variable were to change, **subsequent sync calls** made by other fibers **might not synchronize correctly**. + +Now let's look at a more complex example: + +```javascript +import "io" + +struct Counter { + pub var count + + pub func inc(self) { + sync self { + self.count ++ + } + } +} + +counter := Counter@(0) + +async func incrementer(max) { + for var i = 0; i < max; i++ { + counter.inc() + } +} + +i1 := increment(1000) +i2 := increment(200) + +await i1 +await i2 + +counter.count |> io.print +``` + +This example demonstrates how to use synchronization to avoid concurrency issues when multiple threads or processes attempt to access and modify the same shared resource, in this case, the count variable within the **Counter** instance. \ No newline at end of file diff --git a/docs/Get started/README.md b/docs/Get started/README.md new file mode 100644 index 0000000..a6f9bba --- /dev/null +++ b/docs/Get started/README.md @@ -0,0 +1,9 @@ +--- +title: Get started +sidebar_position: 2 +--- +In this section, we will explore the **fundamental concepts of Argon**, providing you with the **foundational knowledge** necessary to begin writing your initial programs and gaining a deep understanding of how the language operates. + +We'll cover topics such as primitive data types, control structures, functions (closures & generators), structures and traits, error handling, and module imports. + +These **core concepts** will serve as the building blocks for your journey into Argon programming, enabling you to develop proficiency in the language. \ No newline at end of file