Многопоточное приложение GtK+3.0

вот моя проблема: я разрабатываю многопоточное приложение, состоящее из:

  • Поток GUI-> GTK
  • вспомогательный поток --> проверка подключения к серверу JACK
  • RT нить jack--> занимается обработкой звука

Я реализовал виджет кнопки, который получает сигналы только от вспомогательного потока и потока RT, которые изменяют графический интерфейс в своей функции обратного вызова.

Итак, мой вопрос: кто модифицирует графический интерфейс? поток помощника/RT или поток графического интерфейса, в котором я использовал gtk_main()?

Спасибо за ваше сотрудничество!

редактировать: я добавил код /** @file JPLowPassFilter.c * * @brief это простой клиент, который реализует числовой низкочастотный фильтр */

#include "JPLowPassFilter.h" 
jack_port_t *input_port;
jack_port_t *output_port;

jack_default_audio_sample_t tmp;
int first=1;
appData* mainData;
jack_default_audio_sample_t tmp;

/*Code for port_registration_callback */
void registrationPort(jack_port_id_t port, int reg, void *arg)
{
    return;
}

/*Code for client_registration_callback */
void registrationClient(const char* name, int reg, void *arg)
{
    return;

}
/**
 * The process callback for this JACK application is called in a
 * special realtime thread once for each audio cycle.
 * Must not block!
 */
int process (jack_nframes_t nframes, void *arg)
{ 
    int i;
    float alfa=mainData->alfa;
    jack_default_audio_sample_t *in, *out;
    in = jack_port_get_buffer (input_port, nframes);
    out = jack_port_get_buffer (output_port, nframes);
    for( i=0; i<nframes; i++) {
        if(first==1){
            tmp=in[i];
            first=0;
        }
        else{
            tmp=tmp*alfa+(1.0f-alfa)*in[i];
            //tmp=tmp*(1.0f-alfa)+alfa*in[i];
        }
        out[i]=tmp;
    }
    //fprintf (stderr, ".");
    return 0;      
}

/**
 * JACK calls this shutdow_callback if the server ever shuts down or
 * decides to disconnect the client
 */
void jack_shutdown (void *arg)
{ 
    mainData->state=NOT_WORKING;
    jack_port_unregister(mainData->client,input_port);
    jack_port_unregister(mainData->client, output_port);    
    g_signal_emit_by_name (GTK_BUTTON(mainData->init),"clicked");
}

/* JACK calls this function whenever there is an xrun */
int xrun_function(void *arg)
{
    fprintf (stderr, "--XRUN OCCURRED--\n");    
}

void* threadCode(void* val)
{
    const char *client_name = CLIENT_NAME;
    const char *server_name = NULL;
    mainData=(appData*) val;
    /* if server isn't present, don't start it!*/ 
    jack_options_t options =JackNoStartServer; 
    jack_status_t status;
    do{
        /* try to open a client connection to the JACK server */
        mainData->client = jack_client_open (client_name, options, &status, server_name);
        if (mainData->client == NULL) 
        {
            fprintf (stderr, "jack_client_open() failed, "
                 "status = 0x%2.0x\n", status);
            if (status & JackServerFailed) {
                fprintf (stderr, "Unable to connect to JACK server\n");
            }
            sleep(3);
        }
        else
        {
            fprintf (stderr, "Connected to JACK server\n");
            mainData->state=INIT;
            /*  CALLBACKS*/
            jack_set_process_callback (mainData->client, process, 0);
            jack_on_shutdown (mainData->client, jack_shutdown, mainData);
            jack_set_xrun_callback(mainData->client,xrun_function, 0);  
            jack_set_port_registration_callback (mainData->client, registrationPort,NULL);
            jack_set_client_registration_callback(mainData->client, registrationClient,NULL);

            /* PORTS */
            input_port = jack_port_register (mainData->client, "input", JACK_DEFAULT_AUDIO_TYPE,JackPortIsInput, 0);
            output_port = jack_port_register (mainData->client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
            mainData->state=INIT;
            if ((input_port == NULL) || (output_port == NULL)) 
            {
                fprintf(stderr, "no more JACK ports available\n");
                mainData->state=NOT_WORKING;
            }
            /* STARTS */
            else if (jack_activate (mainData->client)) {
                fprintf (stderr, "cannot activate client");
                jack_port_unregister(mainData->client,input_port);
                jack_port_unregister(mainData->client, output_port); 
                mainData->state=NOT_WORKING; 
            }
            else  
            {
                fprintf (stderr, "Client ready to Run\n");
                mainData->state=WORKING;
                mainData->portsName[0]=jack_port_name(input_port);
                mainData->portsName[1]=jack_port_name(output_port);
            }
             //can be written to
                        mainData->inputList=jack_get_ports(mainData->client,NULL, NULL,JackPortIsInput);
                        //can be read from
            mainData->outputList=jack_get_ports(mainData->client,NULL, NULL,JackPortIsOutput);
            g_signal_emit_by_name (GTK_BUTTON(mainData->init),"clicked");
            while(mainData->state==WORKING)
            {
                sleep(5);
                fprintf (stderr, ".");
            }
            fprintf (stderr, "\n");
                jack_free(mainData->inputList);
            jack_free(mainData->outputList);
            mainData->outputList=NULL;
            mainData->inputList=NULL;
            jack_client_close (mainData->client);       
        }
        fprintf (stderr, "---RECONNECT---\n");
    }while(mainData->state==NOT_WORKING);
    fprintf (stderr, "Ended!\n");

    pthread_exit(NULL);
}

person Gionata Benelli    schedule 19.07.2017    source источник
comment
Gtk поддерживает потоки, но не является потокобезопасным. Функции/методы Gtk должны вызываться из основного потока, в котором находится gtk_main/mainloop. Однако GLib является потокобезопасным с некоторыми замечаниями.   -  person José Fonte    schedule 20.07.2017
comment
поэтому я не должен вызывать сигнал из двух других потоков? И все же я не могу понять, какой поток что делает...   -  person Gionata Benelli    schedule 20.07.2017
comment
Да, сигналы вызываются из основного потока. По сути, функции/методы, используемые для обновления графического интерфейса, должны вызываться из основного потока. Мы не можем помочь вам больше без лучшего объяснения или кода по вашему вопросу.   -  person José Fonte    schedule 20.07.2017
comment
следуя вашему совету, я загрузил код, который излучает сигнал, но интерфейс все еще не работает должным образом, на самом деле иногда он не будет правильно обновлять два поля со списком или просто вылетит   -  person Gionata Benelli    schedule 20.07.2017
comment
Итак, мой вопрос: кто модифицирует графический интерфейс? поток помощника/RT или поток графического интерфейса, в котором я использовал gtk_main()? Я не уверен, что это вопрос. Ваша формулировка не дает понять, о чем вы спрашиваете, если что. Вы говорите, что происходят какие-то таинственные модификации, и просите нас мысленно угадать, кто в этом виноват? Или вы спрашиваете, какой поток вы должны использовать для преднамеренного изменения вещей? Кажется, здесь нет ни одного вопроса или постановки проблемы, которые я могу различить.   -  person underscore_d    schedule 20.07.2017
comment
@Джионата Бенелли, эти g_signal_emit_by_name из темы, являются частью проблемы. У вас должен быть другой способ передать информацию из потока в основной поток. Я посмотрю на это...   -  person José Fonte    schedule 20.07.2017
comment
@underscore_d, может быть, я недостаточно ясно выразился, я спрашивал, был ли signal_callback выполнен рабочим потоком или потоком графического интерфейса, основным, даже если сигнал испускается другим потоком.   -  person Gionata Benelli    schedule 20.07.2017
comment
Не уверен, но я думаю, что если вы не используете g_idle_add(), то эмиссия сигнала происходит в потоке, выполняющем эмиссию, и поэтому обработчики этого сигнала могут быть вызваны и оттуда.   -  person underscore_d    schedule 20.07.2017
comment
Я думаю, что это может быть дубликатом Вызываются ли обратные вызовы GTK из основного потока при использовании gtk_main_iteration?   -  person underscore_d    schedule 20.07.2017
comment
@ Хосе Фонте, поскольку данные, которые мне нужно обменять, идут только одним путем, должен ли я сделать gtk_list_store, связанный с каждым полем со списком, глобальным и напрямую изменить его?   -  person Gionata Benelli    schedule 20.07.2017
comment
@GionataBenelli Нет, не используйте функции Gtk в потоках. Простым способом, поскольку это в основном один из способов, может быть использование GLib AsyncQueue, но перед этим я пытаюсь понять необходимость. Зачем вам нужно имитировать сигнал нажатия на кнопку?   -  person José Fonte    schedule 20.07.2017
comment
@ Хосе Фонте, я хотел отделить графический интерфейс от обработки звука, поэтому я подумал использовать этот сигнальный механизм для своего рода асинхронного обратного вызова. Функция, которую я вызываю, просто обновляет поле со списком gtk_list_store of 2, которое показывает jack_ports, доступные из моего клиента. список имен портов (char**) хранится внутри структуры appData   -  person Gionata Benelli    schedule 20.07.2017
comment
@GionataBenelli хорошо, кажется, я понял. Вы хотите обновить пользовательский интерфейс при изменении состояния разъемов. Вы должны разделить проблемы, рабочие не должны обновлять пользовательский интерфейс, а должны информировать основной поток, который будет обновляться соответствующим образом. Ответом будет передача async_queue, которая будет отправлять данные, а g_iddle_add попытается извлечь из очереди, если есть какие-то данные, затем проанализирует их и изменит пользовательский интерфейс. Или просто предоставьте методы состояния, которые основной поток может объединить для обновления пользовательского интерфейса.   -  person José Fonte    schedule 20.07.2017
comment
@JoséFonte, так что, если я понял: я должен отправить некоторые данные в эту асинхронную очередь, а затем извлечь их из этого g_idle_add и изменить свой пользовательский интерфейс, это правильно?   -  person Gionata Benelli    schedule 20.07.2017
comment
@GionataBenelli Да, проверьте мой ответ.   -  person José Fonte    schedule 20.07.2017


Ответы (1)


После комментариев предлагается заменить функции g_signal_emit_by_name на функции g_async_queue_*.

Предположим, что mainData->init указывает на GAsyncQueue, созданную в главном поток вместо фактической кнопки.

Затем вы можете использовать в своей теме:

g_async_queue_push(G_ASYNC_QUEUE(mainData->init), data);

Данные могут содержать простой флаг, указывающий на статус и/или изменение статуса.

Затем в вашем основном потоке при настройке пользовательского интерфейса вы можете добавить обработчик простоя с помощью:

my_queue = g_async_queue_new();
...
g_idle_add ((GSourceFunc) check_async_queue, my_queue); 

и ваш check_async_queue может быть примерно таким:

gboolean check_async_queue (gpointer user_data) {
   gpointer queue_data;

   queue_data = g_async_queue_try_pop (G_ASYNC_QUEUE(user_data));

   if (queue_data != NULL) {
      // We have data, do something with 'queue_data'
      // and update GUI

   } else {
      // no data, probably do nothing

   }

   return TRUE; // can be G_SOURCE_CONTINUE instead of TRUE
}

Возвращаемое значение будет указывать, должна ли функция check_async_queue продолжать работать или нет, поэтому у вас может быть условие для удаления функции.

Это позволит вам иметь простую одностороннюю очередь сообщений, которую вы можете использовать для передачи информации из рабочего потока в основной поток.

person José Fonte    schedule 20.07.2017