Seth Barrett

Daily Blog Post: September 12th, 2023

Zig

September 12th, 2023

Part 11: Concurrency in Zig

Welcome to the eleventh installment of our "Getting Started with Zig on MacOS" series. In this part, we'll dive into concurrency in Zig, a crucial topic for writing efficient and parallelized programs. Zig provides various concurrency features, including coroutines, async/await, message-passing concurrency, and low-level synchronization primitives like mutexes and atomics.

Coroutines and Async/Await

Coroutines in Zig allow you to write asynchronous, non-blocking code using the async and await keywords. An async function can be paused and resumed without blocking the entire program. Here's an example of using async and await:

const std = @import("std");

async fn fetchData(url: []const u8) ![]const u8 {
    const result = try std.net.httpClient().get(url);
    return result.body;
}

fn main() void {
    const url = "https://example.com";

    const fiber = @import("std").fiber;
    const result = fiber.run(async {
        const data = try fetchData(url);
        std.debug.print("Received data: {}\n", .{data});
        return "Done";
    });

    std.debug.print("Fiber result: {}\n", .{result});
}

In this example:

  • We define an async function fetchData that fetches data from a URL using the Zig standard library's HTTP client.
  • In the main function, we use Zig's fibers to run the asynchronous code and print the result.

Message-Passing Concurrency

Zig supports message-passing concurrency through channels. Channels allow different parts of your program to communicate by sending and receiving messages. Here's an example of using channels for concurrency:

const std = @import("std");

fn worker(channel: *std.mem.Channel(i32)) void {
    while (true) {
        const message = channel.receive();
        if (message == -1) {
            break;
        }
        std.debug.print("Received message: {}\n", .{message});
    }
}

fn main() void {
    const channel = std.mem.Channel(i32).init(16);
    const thread = @import("std").thread;

    thread.spawn(worker, &channel);

    for (var i: i32 = 0; i < 10; i += 1) {
        channel.send(i);
    }
    channel.send(-1);

    thread.join(thread.current());
}

In this example:

  • We create a channel that can transmit i32 values.
  • We spawn a worker thread that receives messages from the channel and processes them.
  • In the main function, we send a sequence of i32 values to the worker and signal it to stop by sending -1.

Mutexes and Atomics

For low-level synchronization, Zig provides mutexes and atomic operations. Mutexes are used to protect critical sections of code from concurrent access, while atomic operations provide thread-safe read-modify-write operations on variables. Here's a basic example using a mutex:

const std = @import("std");

var sharedCounter: i32 = 0;
const mutex = std.concurrent.Mutex.init;

fn incrementCounter() void {
    mutex.lock();
    defer mutex.unlock();

    sharedCounter += 1;
}

fn main() void {
    const thread = @import("std").thread;

    const numThreads: usize = 4;
    var threads: [numThreads]thread.Thread = undefined;

    for (var i: usize = 0; i < numThreads; i += 1) {
        threads[i].spawn(incrementCounter);
    }

    for (var i: usize = 0; i < numThreads; i += 1) {
        thread.join(&threads[i]);
    }

    std.debug.print("Shared counter: {}\n", .{sharedCounter});
}

In this example:

  • We define a shared counter and protect it with a mutex to ensure safe concurrent access.
  • WMultiple threads increment the shared counter, and we use mutexes to prevent data races.

What's Next?

In this part, you've explored Zig's concurrency features, including coroutines, async/await, message-passing concurrency with channels, and low-level synchronization with mutexes and atomics. Understanding and effectively using these features is crucial for writing efficient and concurrent Zig programs.

Congratulations on completing our "Getting Started with Zig on MacOS" series! You should now have a solid foundation for writing Zig code, organizing your projects, and tackling various programming challenges. Continue to explore Zig's documentation and build upon the knowledge you've gained to create powerful and efficient applications. Happy coding!