Qt синхронизация потоков

Модератор: Модераторы разделов

Ответить
svas
Сообщения: 203

Qt синхронизация потоков

Сообщение svas »

Есть два класса Class1, потомок QObject, и Class2, потомок QThread. В Class1 есть слот some_slot(char *buf). В Class2 есть сигнал some_signal(char *buf).
В Class1 есть два экземпляра Class2. Class2 принимает Udp пакеты (оба экземпляра на разных интерфейсах) и затем с помощью "emit some_signal" передаёт пришедший пакет основному потоку, который их отображает. У меня три вопроса
1) Если пакеты приходят на 2 интерфейса одновременно, то в buf в одном из случаев получается каша. Как это исправить?
2) Вообще можно так делать? с помощью emit передавать данные в основной поток?
3) Если нет, то как сделать чтобы при приходе пакета, данные передать основному потоку?
Спасибо сказали:
vadiml
Сообщения: 446
ОС: fc12.x86_64
Контактная информация:

Re: Qt синхронизация потоков

Сообщение vadiml »

Синхронизация потоков через сиглалы-слоты -- далеко не лучший вариант, т.к. это фактически прямой вызов функции.

создай разделяемую память, и пусть одни потоки в неё пишет, другой из нее читает
это позволит избежать блокировок

для каждой пары классов -- своя разделяемая память. Основной класс можешь отправлять поспать, а по выходу из сна он будет проверять появились ли данные. Если ничего нет -- далее спать.
phenom x4 905e, asus m4a79 deluxe, 4 gb, ati x550, ati 4350, 2 x 17" LCD
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

А если основной класс - главное окно приложения, и если его "отправить поспать", окно будет реагировать на пользовательские сообщения, например на нажатие клавиши или кнопки мыши?
Спасибо сказали:
vadiml
Сообщения: 446
ОС: fc12.x86_64
Контактная информация:

Re: Qt синхронизация потоков

Сообщение vadiml »

а это смотря как Вы его отправите спать, если просто скажите ждать таймер, то он попутно и другими делами сможет заниматься. Но если у Вас промежутки будут маленькими, то таймер желательно останавливать

ЗЫ вообще всё есть в документации по Qt, так же там есть примеры использования многопоточности
phenom x4 905e, asus m4a79 deluxe, 4 gb, ati x550, ati 4350, 2 x 17" LCD
Спасибо сказали:
v04bvs
Сообщения: 636
ОС: Debian GNU/Linux

Re: Qt синхронизация потоков

Сообщение v04bvs »

vadiml писал(а):
31.01.2008 16:25
Синхронизация потоков через сиглалы-слоты -- далеко не лучший вариант, т.к. это фактически прямой вызов функции.

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

Поэтому вариант вполне жизнеспособный.
Спасибо сказали:
vadiml
Сообщения: 446
ОС: fc12.x86_64
Контактная информация:

Re: Qt синхронизация потоков

Сообщение vadiml »

Я бы не был так категоричен -- посмотри что из себя представляет макрос Q_OBJECT и исходники классов QObject и QMetaObject (файл qobject.cpp)

я писал эмуляцию поведения Q_OBJECT и видел как это работает в отладчике, от emit signal до вызова slot не так много вызовов.

кстати, там относительно простая структура, благодаря чему возможно для разных экземпляров одного класса иметь разный набор сигналов-слотов.
phenom x4 905e, asus m4a79 deluxe, 4 gb, ati x550, ati 4350, 2 x 17" LCD
Спасибо сказали:
v04bvs
Сообщения: 636
ОС: Debian GNU/Linux

Re: Qt синхронизация потоков

Сообщение v04bvs »

vadiml писал(а):
04.02.2008 15:39
Я бы не был так категоричен -- посмотри что из себя представляет макрос Q_OBJECT и исходники классов QObject и QMetaObject (файл qobject.cpp)

я писал эмуляцию поведения Q_OBJECT и видел как это работает в отладчике, от emit signal до вызова slot не так много вызовов.

И смотрел, и трейсил, и хакал. См. здесь: http://doc.trolltech.com/4.3/threads.html#...-across-threads
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

Ну подскажите, как можно из одного потока в другой передать большой массив, длиной 0xffff? А то, как я уже говорил, если приходят пакеты почти одновременно, или каша в одном из пакетов, или оба пакета одинаковых
Кстати в ссылке выше говорится про Qt 4.3, у меня 4.2, это имеет значение?
Спасибо сказали:
v04bvs
Сообщения: 636
ОС: Debian GNU/Linux

Re: Qt синхронизация потоков

Сообщение v04bvs »

svas писал(а):
05.02.2008 04:25
Ну подскажите, как можно из одного потока в другой передать большой массив, длиной 0xffff? А то, как я уже говорил, если приходят пакеты почти одновременно, или каша в одном из пакетов, или оба пакета одинаковых

Покажите код. Так мало что понятно.

Кстати в ссылке выше говорится про Qt 4.3, у меня 4.2, это имеет значение?


Вряд ли.
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

class1.h

Код: Выделить всё

#ifndef CLASS1_H
#define CLASS1_H

#include <QThread>
#include <QReadWriteLock>

class Class1 : public QThread
{
    Q_OBJECT
    public:
        Class1(QString group, QString iface, int id);
        void stop();
        void setMulticast();
        u_char buf[0xffff];
    signals:
        void recvPack(int id, int size);
    protected:
        void run();
    private:
        void initNet();
        bool stopped;
        int ident;
        QReadWriteLock lock;
        QString group;
        QString iface;
        int port;
        int sock;
};

#endif


class1.cpp

Код: Выделить всё

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "class1.h"

Class1::Class1(QString g, QString i, int id)
{
    stopped = false;
    group = g;
    iface = i;
    ident = id;
    port = 50138;
    initNet();
    start();
}

void Class1::run()
{
    while (1)
    {
        lock.lockForWrite();
        if (stopped)
        {
            stopped = false;
            break;
        }
        lock.unlock();

        sockaddr_in cli;
        int recvbyte;
        socklen_t addrsize;
        addrsize = sizeof(cli);

        recvbyte = recvfrom(sock, buf, 0xffff, 0, (struct sockaddr *)&cli, &addrsize);
        emit recvPack(ident, recvbyte);
    }
}

void Class1::stop()
{
    lock.lockForWrite();
    stopped = true;
    close(sock);
    lock.unlock();
}

void Class1::setMulticast()
{
    struct ip_mreq mreq;
    bzero(&mreq, sizeof(mreq));
    inet_aton((const char *)(group.toLocal8Bit()), &mreq.imr_multiaddr);
    inet_aton((const char *)(iface.toLocal8Bit()), &mreq.imr_interface);
    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
}

void Class1::initNet()
{
    struct sockaddr_in srv;
    bzero(&srv, sizeof(srv));
    srv.sin_family = AF_INET;
    srv.sin_port = htons(port);
    inet_aton((const char *)(group.toLocal8Bit()), &srv.sin_addr);

    sock = socket(AF_INET, SOCK_DGRAM, 0);

    bind(sock, (struct sockaddr *)&srv, sizeof(srv));
}


class2.h

Код: Выделить всё

#ifndef CLASS2_H
#define CLASS2_H

#include <QObject>
#include <QString>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "class1.h"

class Class2 : public QObject
{
    Q_OBJECT
    public:
        Class2(QString group, QString iface, QObject *parent = 0);
        u_char data[0xffff];
    private slots:
        void recvPack(int id, int size);
    private:
        Class1 *multicastThread;
        Class1 *ifaceThread;
};

#endif


class2.cpp

Код: Выделить всё

#include "class2.h"

Class2::Class2(QString group, QString iface, QObject *parent) : QObject(parent)
{
    multicastThread = new Class1(group, iface, 1);
    multicastThread->setMulticast();

    ifaceThread = new Class1(iface, "", 2);

    connect(multicastThread, SIGNAL(recvPack(int, int)), this, SLOT(recvPack(int, int)));
    connect(ifaceThread, SIGNAL(recvPack(int, int)), this, SLOT(recvPack(int, int)));
}

void Class2::recvPack(int id, int size)
{
    if (id == 1)
    {
        memcpy(data, multicastThread->buf, size);
    }
    else if (id == 2)
    {
        memcpy(data, ifaceThread->buf, size);
    }

    for (int i=0; i<size; i++)
    {
        if ((i % 16) == 0)
        {
            fprintf(stderr,"\n");
        }

        if (data[i] < 16)
        {
            fprintf(stderr,"0x0%x, ",data[i]);
        }
        else
        {
            fprintf(stderr,"0x%x, ",data[i]);
        }
    }
    fprintf(stderr,"\n");
}



Я здесь тока Class1 и Class2 поменял
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

Ну помогите! Третий день бьюсь, не знаю что делать...
Спасибо сказали:
v04bvs
Сообщения: 636
ОС: Debian GNU/Linux

Re: Qt синхронизация потоков

Сообщение v04bvs »

В коде невооружённым глазом видны кучи гонок.

Попробую перечислить.

Поток запускается сразу же в конструкторе. Там же инициализируется сокет. Представьте такую ситуацию:
multicastThread = new Class1(group, iface, 1);
<...>
Здесь ОС приостанавливает главный поток и передаёт управление потоку Class1. Он тут же инициализирует сеть, принимает данные и посылает сигнал recvPack. Но сигнал ещё ни с чем не соединён, упс. Данные пропали.
<...>
connect(ifaceThread, SIGNAL(recvPack(int, int)), this, SLOT(recvPack(int, int)));


Аналогично со вторым потоком.

Метод stop просто некорректен.

Но самое интересное начинается здесь.
recvbyte = recvfrom(sock, buf, 0xffff, 0, (struct sockaddr *)&cli, &addrsize);
emit recvPack(ident, recvbyte);

Разберёмся, что здесь происходит. Мы считываем 65535 байтов и посылаем сигнал другому потоку. Сигнал ложится в очередь сообщений другого потока. Мы пока ещё работаем. Идём на следующий заход, опять считываем данные и опять посылаем сигнал.
Теперь ОС передаёт управление главному потоку. Главный поток проверяет свою очередь сообщений, видит наш первый сигнал, читает его и вызывает соответствующую функцию. Вот только в этой функции уже буфер buf перезаписан данными следующего пакета. Упс.

Я вижу два варианта: либо лочить ресурс, пока его не считали (тогда будет тратиться меньше памяти, но будут большие проблемы с масштабируемостью), либо копировать весь буфер и передавать его в качестве аргумента (больше памяти, но лучше масштабируемость).

А вообще написано очень небрежно. Параллельное программирование не любит такого стиля, у вас будет много подобных проблем. Старайтесь писать очень аккуратно.
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

"Небрежно" - можно поконкретнее? Программированием не так давно занимаюсь, хотелось бы знать, что не так
И как можно скопировать весь буфер и передать его в качестве парметра? Выделить память, скопировать в неё весь массив и передать указатель в качестве параметра?
В основном потоке можно будет потом эту память освободить или надо в том же потоке?
Спасибо сказали:
v04bvs
Сообщения: 636
ОС: Debian GNU/Linux

Re: Qt синхронизация потоков

Сообщение v04bvs »

svas писал(а):
07.02.2008 04:09
"Небрежно" - можно поконкретнее?

ОК. Перечислю всё, что мне не понравилось.
1. Параметры типа QString передаются по значению. Это плохо. Практически всегда все параметры непримитивного типа (примитивные типы - int, float, ...) нужно передавать по константной ссылке.
2. Имеется public-поле. Это нарушает один из основных принципов ООП - инкапсуляцию.
3. В Qt есть свои средства для работы с сокетами, не уверен, что они вам подойдут, но можно попробовать.
4. Нет вообще никакой обработки ошибок.
5. Не совсем удачный дизайн классов. Я бы сделал два подкласса класса Class1. Между ними хоть различие хоть и небольшое, но есть. По моему опыту в дальнейшем оно будет только увеличиваться.
6. Есть такая штука - список инициализации конструктора, лучше её применять, чем в теле присваивать.

И как можно скопировать весь буфер и передать его в качестве парметра? Выделить память, скопировать в неё весь массив и передать указатель в качестве параметра?

Да.
В основном потоке можно будет потом эту память освободить или надо в том же потоке?

Где угодно можно, glibc потокобезопасна.

А ещё лучше использовать класс QVector, он сам всё, что надо, скопирует и освободит.
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

Спасибо.
3. В Qt есть свои средства для работы с сокетами, не уверен, что они вам подойдут, но можно попробовать.


Я смотрел, не мог найти как сделать чтобы сокет пакеты принимал предназначенные адресу 234.0.0.1

Обработка ошибок есть, я просто не стал писать её.
Спасибо ещё раз
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

Спасибо.
3. В Qt есть свои средства для работы с сокетами, не уверен, что они вам подойдут, но можно попробовать.

Я смотрел, не мог найти как сделать чтобы сокет пакеты принимал предназначенные адресу 234.0.0.1

Обработка ошибок есть, я просто не стал писать её.
Спасибо ещё раз
Спасибо сказали:
svas
Сообщения: 203

Re: Qt синхронизация потоков

Сообщение svas »

Пытаюсь подключить <QUdpSocket>, говорит No such file or directory
Тоже самое на <QtNetwork>
В папке /usr/include/qt4 все эти файлы есть.
Спасибо сказали:
Ответить