Lock-free data structures

The only remaining type to explain is SegQueue, from the crossbeam crate. This type is a lock-free queue, meaning that it can be used concurrently by multiple threads without a lock. The implementation of lock-free data structures is beyond the scope of this book, but it suffices to say that it uses atomic operations behind the scenes so that we don't need to use a Mutex to mutate this value in mutable threads at the same time. We still need to wrap this queue in an Arc to be able to share it with multiple threads.

We're using a lock-free data structure because we'll be constantly checking whether there's a new element in this queue while possibly adding new elements to this queue from another thread. If we were to use Mutex<VecDeque<Action>>, it would be less efficient because calling lock() on Mutex waits if the lock is held by another thread.

Let's get back to our event loop. Let's add a constructor for EventLoop:

impl EventLoop {
    fn new() -> Self {
        EventLoop {
            queue: Arc::new(SegQueue::new()),
            playing: Arc::new(Mutex::new(false)),
        }
    }
}

This constructor simply creates the queue and the Boolean wrapped in a Mutex.

Before we use it, we'll create a State structure that will contain various data shared between the GUI thread and the music player thread, put this code in the main module:

struct State {
    stopped: bool,
}

Also, add a state field in the App structure:

struct App {
    adjustment: Adjustment,
    cover: Image,
    playlist: Rc<Playlist>,
    state: Arc<Mutex<State>>,
    toolbar: MusicToolbar,
    window: Window,
}

This requires a new import statement:

use std::sync::{Arc, Mutex};

Since this value will be shared with another thread, we need to wrap it in Arc<Mutex>. Then, in the constructor, create this value and assign it to this new field, while also sending it to the Playlist constructor:

impl App {
    fn new() -> Self {
        // …

        let state = Arc::new(Mutex::new(State {
            stopped: true,
        }));

        let playlist = Rc::new(Playlist::new(state.clone()));

        // …

        let app = App {
            adjustment,
            cover,
            playlist,
            state,
            toolbar,
            window,
        };

        // …
    }
}

Let's update the Playlist constructor:

impl Playlist {
    pub(crate) fn new(state: Arc<Mutex<State>>) -> Self {
        let model = ListStore::new(&[
            Pixbuf::static_type(),
            Type::String,
            Type::String,
            Type::String,
            Type::String,
            Type::String,
            Type::String,
            Type::String,
            Pixbuf::static_type(),
        ]);
        let treeview = TreeView::new_with_model(&model);
        treeview.set_hexpand(true);
        treeview.set_vexpand(true);

        Self::create_columns(&treeview);

        Playlist {
            model,
            player: Player::new(state.clone()),
            treeview,
        }
    }
}

The structure requires a new field, so let's add it:

pub struct Playlist {
    model: ListStore,
    player: Player,
    treeview: TreeView,
}

This also needs new import statements:

use std::sync::{Arc, Mutex};

use State;
use player::Player;

We use the pub(crate) syntax to silent an error. Since we're using a private type (State) in a public method, the compiler throws an error. This syntax means that the function is public to the other modules of the crate, but other crates cannot access it. Here, we only send the state to the Player constructor, which we will implement right away.