Как добавить в GLib.List из другого потока в Vala

У меня есть GLib.List, в который я хочу добавить элементы.
Я хочу добавить эти элементы одновременно, используя несколько GLib.Threads

Я пытаюсь синхронизировать доступ к списку с помощью GLib.Mutex. Синхронизация работает, но элементы не добавляются.

public static void main() {
    var list = new GLib.List<string>();
    var mutex = GLib.Mutex();

    var workerA = new Worker("A", list, mutex);
    var workerB = new Worker("B", list, mutex);
    var workerC = new Worker("C", list, mutex);

    GLib.Thread<void*> tA = new GLib.Thread<void*>("WorkerThread", workerA.run);
    GLib.Thread<void*> tB = new GLib.Thread<void*>("WorkerThread", workerB.run);
    GLib.Thread<void*> tC = new GLib.Thread<void*>("WorkerThread", workerC.run);

    tA.join();
    tB.join();
    tC.join();

    stdout.printf("List:\n");
    foreach (string str in list) {
        stdout.printf(" - %s\n", str);
    }
}

class Worker : GLib.Object {
    private string name;
    private weak GLib.List<string> list;
    private weak GLib.Mutex mutex;

    public Worker(string name, GLib.List<string> list, GLib.Mutex mutex) {
        this.name = name;
        this.list = list;
        this.mutex = mutex;
    }

    public void* run() {
        mutex.lock();
        list.append(name);
        mutex.unlock();

        return null;
    }
}

Когда я смотрю на часть синхронизации, кажется, что она работает правильно (даже с большим количеством потоков), но элементы не добавляются в список!

Выход:

Список:

Может кто-нибудь, пожалуйста, скажите мне, как это сделать?


person Ansgar    schedule 26.11.2014    source источник


Ответы (2)


GLib.List немного странно. Метод append фактически изменяет указатель list, а не то, на что он указывает. Если вы хотите, чтобы это работало, вам нужно:

  1. Поместите список в общедоступное место (например, сделайте его полем класса, общим для всех потоков, или глобальной переменной).
  2. Вместо этого используйте Gee.List из пакета libgee. В общем, структуры данных в libgee гораздо проще использовать в Vala, чем их аналоги в glib.
person apmasell    schedule 26.11.2014

Благодаря тому, что apmasell указал, что вещь не работает, на самом деле GLib.List я просмотрел исходный код C.

Он прав: метод append изменяет указатель - но только (!) если GLib.List пуст!
Итак, если не считать превращения списка в глобальную переменную или использования другой реализации списка, я думаю, что это лучшая работа. Обходной путь — просто добавить один элемент перед передачей списка в поток.
После завершения всех потоков вы можете просто снова удалить элемент.

person Ansgar    schedule 27.11.2014
comment
Вызов append — это операция O(N). Было бы намного лучше вызвать prepend (что равно O(1)), а затем перевернуть список, когда вы закончите. Конечно, это портит хак, который вы описали выше — возможно, вам следует вместо этого просто использовать GLib.Sequence или GLib.Tree, которые не имеют странной семантики и оба O (log N) для вставки. - person nemequ; 27.11.2014