crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )


  Ответ в QMap + QThread
Введите ваше имя
Подтвердите код

Введите в поле код из 6 символов, отображенных в виде изображения. Если вы не можете прочитать код с изображения, нажмите на изображение для генерации нового кода.
 

Опции сообщения
 Включить смайлы?
Иконки сообщения
(Опционально)
                                
                                
  [ Без иконки ]
 


Последние 10 сообщений [ в обратном порядке ]
AXELman4ever Дата 3.11.2011, 11:28
 
Цитата(ssoft @ 3.11.2011, 7:45) *
Например, в реализации Qt для QMap сначала физически удаляются данные (вызывается деструктор), связанные с элементом, а затем правится структура QMap.

Так вот, без мьютекса, если метод чтения потока 2 будет вызван после удаления данных потоком 1, но до изменения структуры данных потоком 1, то это и может привести к крешу.


Блин, действительно же, как я мог забыть что QMap постоянно производит реструктуризацию своего дерева. Спасибо, Вам большое. Я открыл для себя очень многое, благодаря Вашим ответам :)
ssoft Дата 3.11.2011, 7:45
 
Цитата(AXELman4ever @ 3.11.2011, 0:13) *
То есть, если не лочить процесс изъятия элемента из карты, то вызванный метод ненароком может прочесть 1 элемента, а изъять совсем другой?

Верно абсолютно.

Цитата(AXELman4ever @ 3.11.2011, 0:13) *
А если элемент не извлекать вовсе, а только считывать его? Каким образом отсутствие мьютекса, в таком случае, может вызвать краш?


Предположим поток 1 записывает данные, а поток 2 считывает.
Запись данных приводит к вызову метода, внутри которого изменяются сами данные и структура их хранения.

Например, в реализации Qt для QMap сначала физически удаляются данные (вызывается деструктор), связанные с элементом, а затем правится структура QMap.

Так вот, без мьютекса, если метод чтения потока 2 будет вызван после удаления данных потоком 1, но до изменения структуры данных потоком 1, то это и может привести к крешу.
AXELman4ever Дата 2.11.2011, 23:13
  Замечательно, вижу свет в конце тоннеля ^_^ За что, Вам спасибо!

Хочу еще уточнить касательно мьютексов, на своем же примере.

Из множества потоков производится вызов метода на запись в экземпляр QMap, и всего 1 поток вызывает метод на их считывание по одному элементу с периодичностью в 1 секунду. После того как запись была изъята - этот элемент удаляется.

То есть, если не лочить процесс изъятия элемента из карты, то вызванный метод ненароком может прочесть 1 элемента, а изъять совсем другой?

А если элемент не извлекать вовсе, а только считывать его? Каким образом отсутствие мьютекса, в таком случае, может вызвать краш?
ssoft Дата 2.11.2011, 17:17
  Начиналось все с того, что

Цитата(AXELman4ever @ 27.10.2011, 13:21) *
1-ое:
Имеется 2 дочерних потока ("вытекающие" из мейн потока). Цель такова:

1 поток должен заносить значения в карту (QMap),
2 поток должен эти значения cчитывать.


Итак, если поток 2 должен иметь доступ к актуальным данным (даже на чтение), то без мьютексов не обойтись.

Цитата(AXELman4ever)
Но ведь (1) справедливо только для прямого вызова (используя Qt::DirectConnection), а если вести речь о вызове через сигнал/слот используя Qt::QueuedConnection?


Да все сработает, только нужно всегда помнить, что с сигналом tryToDoSmth() класса MyThread можно коннектится только как Qt::QueuedConnection. И обязательно указывать это в сопроводительной документации, если кто-то этим классом кроме Вас будет пользоваться. Просто не удобно.

Цитата(AXELman4ever)
Об очереди у меня сформировалось такое понятие - что каждый (не прямой) вызов метода из другого потока становится в очередь на выполнение в главном потоке. И когда главный поток берет на себя управление, он пересматривает очередь и выполняет в своем потоке все те задачи на выполнение, которые набежали от дочерних потоков.


Если соединение между сигналом и слотом выполнено с параметром Qt::QueuedConnection (в случае когда объекты связаны с разными потоками Qt::AutoConnection == Qt::QueuedConnection),

QObject::connect( obj1, SIGNAL( doFoo() ), obj2, SLOT( foo() ), Qt::QueuedConnection );

obj1 связан с thread1
obj2 связан с thread2

то при вызове сигнала doFoo() объекта obj1 формируется сообщение о необходимости вызова слота foo() у объекта obj2.
Это сообщение потоком thread1 помещается в очередь потока thread2. Когда thread2 доходит до обработки этого сообщения, тогда и вызывается слот foo().
Так что поняли, вроде, как правильно.

Просто ранее было написано, что к данным необходим доступ на чтение из дочернего потока.
AXELman4ever Дата 1.11.2011, 18:23
 
Цитата
Опасность в одновременном не синхронизированном доступе к данным из разных потоков, и как следствие возможном нарушения их целостности и крешу программы.


Спасибо, я так и предполагал. А синхронизировать доступ можно через выше перечисленные Вами приблуды. Просто требовался подтверждение этому из Ваших "уст". ^_^

1:
Цитата
И выполняться он будет, там же, в дочернем потоке.
Где методы вызываются, там они и выполняются.

2:
Цитата
но перехватывать вызов методов и перенаправлять их в очередь сообщений Qt не умеет.


Но ведь (1) справедливо только для прямого вызова (используя Qt::DirectConnection), а если вести речь о вызове через сигнал/слот используя Qt::QueuedConnection?

Ведь документация гласит о Qt::QueuedConnection что при таком подключении The slot is executed in the receiver's thread. Как я понимаю это имеет прямое отношение к пункту 2.

Об очереди у меня сформировалось такое понятие - что каждый (не прямой) вызов метода из другого потока становится в очередь на выполнение в главном потоке. И когда главный поток берет на себя управление, он пересматривает очередь и выполняет в своем потоке все те задачи на выполнение, которые набежали от дочерних потоков.

Если мое понимание не есть верным, очень прошу Вас, докажите мне что это так. Спасибо.
ssoft Дата 1.11.2011, 16:45
 
Цитата(AXELman4ever @ 1.11.2011, 14:33) *
"Метод объекта главного потока будет вызываться напрямую из дочернего потока", а выполняться он будет там же (в дочернем), или он оттуда будет только вызываться, а выполняться будет через очередь?


И выполняться он будет, там же, в дочернем потоке.
Где методы вызываются, там они и выполняются.

Очереди сообщений и реализованный через них механизм сигнал-слот взаимодействия - это все проделки Qt.

Цитата(AXELman4ever @ 1.11.2011, 14:33) *
А ведь сигнал принадлежит объекту, который в свою очередь принадлежит главному потоку


Сигналы и слоты - это обычные методы классов С++.
В С++ объекты не принадлежат никаким потокам.
Сопоставление QObject конкретному потоку реализовано непосредственно внутри Qt, но перехватывать вызов методов и перенаправлять их в очередь сообщений Qt не умеет.
Поэтому в каком потоке вызываем метод - в таком и выполняем (хоть сигнал, хоть слот, хоть обычный метод).

Цитата(AXELman4ever @ 1.11.2011, 14:33) *
А в чем заключается опасность?

Опасность в одновременном не синхронизированном доступе к данным из разных потоков, и как следствие возможном нарушения их целостности и крешу программы.
AXELman4ever Дата 1.11.2011, 13:33
  Премного благодарен за толковое разъяснение.

1. Что касается замечаний, то пример был состряпан за 5 минут, без какой-либо нужды корректно (или вообще) завершать потоки, потому триггер, регулирующий правильное его завершение я не использовал, но с данным методом я знаком.

2. Тоже самое могу сказать и про отсутствие указателя на родителя в объекте-наследнике QThread, по причине что я не задавался целью удалять объект в процессе работы программы, он ведь сам удалится по её завершению. К тому же, можно исправить ситуацию через delete, ему ведь все равно, есть у объекта родитель или его нет.

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

По 3 замечанию всё понятно ^_^


Но появилось еще несколько неясностей:

Цитата
Сигнал

emit tryToDoSmth();



в данном случае опасен, т.к. он принадлежит объекту из главного потока, а вызывается в дочернем.


Так именно это мне и требуется. А в чем заключается опасность?

Цитата
... Qt::AutoConnection == Qt::DirectConnection и метод объекта главного потока будет вызываться напрямую из дочернего потока.


"Метод объекта главного потока будет вызываться напрямую из дочернего потока", а выполняться он будет там же (в дочернем), или он оттуда будет только вызываться, а выполняться будет через очередь?

Цитата
Прямой вызов - это непосредственный вызов слота, как-будто бы метод вызывается явно. Поэтому где он вызван, в том потоке он и выполняется.


А где он вызван? Там, откуда испускается сигнал? А ведь сигнал принадлежит объекту, который в свою очередь принадлежит главному потоку, и согласно выше сказанному, сигнал уже не имеет отношения к дочернему потоку, а может только испускаться из него - это никак не влияет на то, где выполнится метод?






Блин, чем глубже я копаю, тем больше сомневаюсь в том, что я хоть что-то понимал :(
ssoft Дата 1.11.2011, 8:46
  В такой реализации скрыта серьезная ошибка.
Правда ее допускают вначале практически 95% программистов.

Объясняю:

Объект QThread - это класс "обертка" вокруг реального потока.
Как и любой объект QObject он принадлежит тому потоку, в котором он создан, в данном случае главному потоку.

Сигнал

emit tryToDoSmth();


в данном случае опасен, т.к. он принадлежит объекту из главного потока, а вызывается в дочернем.
Поэтому здесь Qt::AutoConnection == Qt::DirectConnection и метод объекта главного потока будет вызываться напрямую из дочернего потока.

Если написать явно Qt::QueuedConnection, то проблем быть не должно, тюк. метод будет вызываться через очередь.

Цитата
И вот как я понимаю это, есть доступ прямой - когда много потоков вызывают 1 метод не принадлежащий ему, и этот метод выполняет главный поток, и синхронный - когда выполнение метода переносится в другие потоки которые выполняют его синхронно по отношению друг к другу.


Методы не принадлежат потокам).

Прямой вызов - это непосредственный вызов слота, как-будто бы метод вызывается явно. Поэтому где он вызван, в том потоке он и выполняется.
Разные потоки всегда работают асинхронно, для их синхронизации и используются всякие приблуды типа мьютексов и семафоров.

Сделаю еще несколько замечаний:

1. Так писать не нужно, т.к. поток никогда корректно не завершиться.

void run()
{
   while(true){
       msleep(150);    
       emit tryToDoSmth();
   }
}


Нужно либо так

void run()
{
   while( not_stoped ){
       msleep(150);    
       emit tryToDoSmth();
   }
}

void setStopFlag ( bool yes )
{
    not_stoped = yes;
}


Либо так

void run()
{
    QTimer notifier; //  notifier принадлежит дочернему потоку, this принадлежит главному
    connect( &timer, SIGNAL( timeout() ), this, SIGNAL( tryToDoSmth() );
    notifier.start( 150 );
    exec();
}


2. В инициализации дочернего потока

MyThread *thread = new MyThread();


не указан родитель, т.о. thread никогда не удалиться (будут лики памяти)
Можно записать так

MyThread *thread = new MyThread( QCoreApplication::instance() );


3. Здесь очередь не запускается

thread->start();        //запускаем очередь потока


Очередь запускается только методом exec().
AXELman4ever Дата 31.10.2011, 19:11
  1:
Уточню:

QMap принадлежит только главному потоку... QMap, не является общим для потоков и только главный поток читает данные из него. Но существует дочерний поток, который хочет записать в него данные, потому испускает сигнал с параметрами на слот главного потока где и происходит insert в QMap.


Хочу привести начальный пример. Я покаместь опущу использование Q(Multi)Map, по скольку понимаю что проблема заключается в непонимании принципа вызова методов из потока.

Очень прошу Вас немного помучаться со мной :rolleyes:


Существует слот в главном потоке:
void doSmthing()
{
   qDebug() << QThread::currentThread();
}


существует 1 дочерний поток в котором run() описан следующим образом:

void run()
{
   while(true){
       msleep(150);    
       emit tryToDoSmth();
   }
}


в главном треде инициализирован дочерний поток и описан коннект для него:

MyThread *thread = new MyThread();
thread->start();        //запускаем очередь потока

connect(thread, SIGNAL(tryToDoSmth()), this, SLOT(doSmthing()));


Так вот, если в коннекте указать Qt::DirectConnection то согласно QThread::currentThread() выполнение метода произойдет в дочернем потоке, а при Qt::QueuedConnection - в главном. Разве не так?

И вот как я понимаю это, есть доступ прямой - когда много потоков вызывают 1 метод не принадлежащий ему, и этот метод выполняет главный поток, и синхронный - когда выполнение метода переносится в другие потоки которые выполняют его синхронно по отношению друг к другу.

2:
Задача подразумевает что: QMap принадлежит только(одному) главному потоку, и существует еще 10 потоков, которые дергают метод главного потока сигналом с параметрами. То есть главный поток все время получает параметры на слот и вкладывает их в QMap.

Так вот, вопрос в следующем: Вызов методов QMap в ситуации как я описал ранее, в 1 пункте этого поста - в любом случае будет принадлежать чужому потоку?Независимо от того стоит там Qt::DirectConnection или же Qt::DirectConnection, всеравно метод insert для QMap выполнится в чужом потоке?


UDP: Я прошу прощения, не ругайте сильно за чушь, если где была такая. Все мы были такими, все чего-то не знали или не понимали. Просто хотелось бы понять что к чему в подробностях, а не по принципу "ну раз работает - значит так надо" :)
Спасибо.
ssoft Дата 31.10.2011, 13:53
 
Цитата(AXELman4ever @ 31.10.2011, 14:47) *
Так, понял. Но это в случае если метод, в котором осуществляется доступ к QMap будет выполнен в разных потоках. А вот когда оба потока вызовут метод на выполнение в главном потоке, а не каждый в своем, то ведь как не крути - очередь у главного потока одна, и все запросы на изменение объекта QMap буду выполнятся по-очередно, ведь так?


Нет, не так.
Вызов методов QMap прямой и синхронный и каждый в своем потоке, а не через очередь сообщений.

Цитата(AXELman4ever @ 31.10.2011, 14:47) *
оба потока вызовут метод на выполнение в главном потоке, а не каждый в своем


А это как возможно? Если только главный поток содержит некий диспетчер для QMap.))

Цитата(AXELman4ever @ 31.10.2011, 14:47) *
К примеру, 2 потока отправляют сигнал с параметрами на слот главного потока.


Тогда QMap не является общими данными для разных потоков, а является данными для главного потока.
Смотря как нужно для реализации.

Пусть изменения можно внести и так, а как быть с получением данных от QMap??? Или данные QMap в разных потоках не нужны, тогда зачем вообще городить огород с потоками, когда можно все реализовать в главном потоке???
Просмотр темы полностью (откроется в новом окне)
RSS Текстовая версия Сейчас: 29.4.2024, 19:46