Skip to content

Commit

Permalink
feat: asynchronous programming
Browse files Browse the repository at this point in the history
  • Loading branch information
jacopodl committed Sep 9, 2023
1 parent 4e21f03 commit 498f59a
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 0 deletions.
18 changes: 18 additions & 0 deletions docs/Async programming/README.md
Original file line number Diff line number Diff line change
@@ -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.
48 changes: 48 additions & 0 deletions docs/Async programming/async.md
Original file line number Diff line number Diff line change
@@ -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()`.
88 changes: 88 additions & 0 deletions docs/Async programming/chan.md
Original file line number Diff line number Diff line change
@@ -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.
35 changes: 35 additions & 0 deletions docs/Async programming/spawn.md
Original file line number Diff line number Diff line change
@@ -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!")
}()
}
```
61 changes: 61 additions & 0 deletions docs/Async programming/syncblock.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions docs/Get started/README.md
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 498f59a

Please sign in to comment.