QMap + QThread |
Здравствуйте, гость ( Вход | Регистрация )
QMap + QThread |
AXELman4ever |
31.10.2011, 13:47
Сообщение
#11
|
Студент Группа: Участник Сообщений: 31 Регистрация: 22.9.2011 Пользователь №: 2902 Спасибо сказали: 0 раз(а) Репутация: 0 |
Если обращаться к объекту через его копию ( QMap object ), тогда у каждого потока будет свой собственный экземпляр данных никак не связанных с данными другого потока. Креша не будет, но данные будут разные. Так, понял. Но это в случае если метод, в котором осуществляется доступ к QMap будет выполнен в разных потоках. А вот когда оба потока вызовут метод на выполнение в главном потоке, а не каждый в своем, то ведь как не крути - очередь у главного потока одна, и все запросы на изменение объекта QMap буду выполнятся по-очередно, ведь так? К примеру, 2 потока отправляют сигнал с параметрами на слот главного потока. Сообщение отредактировал AXELman4ever - 31.10.2011, 13:48 |
|
|
ssoft |
31.10.2011, 13:53
Сообщение
#12
|
Участник Группа: Участник Сообщений: 130 Регистрация: 17.2.2010 Из: Москва Пользователь №: 1470 Спасибо сказали: 30 раз(а) Репутация: 3 |
Так, понял. Но это в случае если метод, в котором осуществляется доступ к QMap будет выполнен в разных потоках. А вот когда оба потока вызовут метод на выполнение в главном потоке, а не каждый в своем, то ведь как не крути - очередь у главного потока одна, и все запросы на изменение объекта QMap буду выполнятся по-очередно, ведь так? Нет, не так. Вызов методов QMap прямой и синхронный и каждый в своем потоке, а не через очередь сообщений. оба потока вызовут метод на выполнение в главном потоке, а не каждый в своем А это как возможно? Если только главный поток содержит некий диспетчер для QMap.)) К примеру, 2 потока отправляют сигнал с параметрами на слот главного потока. Тогда QMap не является общими данными для разных потоков, а является данными для главного потока. Смотря как нужно для реализации. Пусть изменения можно внести и так, а как быть с получением данных от QMap??? Или данные QMap в разных потоках не нужны, тогда зачем вообще городить огород с потоками, когда можно все реализовать в главном потоке??? Сообщение отредактировал ssoft - 31.10.2011, 14:01 |
|
|
AXELman4ever |
31.10.2011, 19:11
Сообщение
#13
|
Студент Группа: Участник Сообщений: 31 Регистрация: 22.9.2011 Пользователь №: 2902 Спасибо сказали: 0 раз(а) Репутация: 0 |
1:
Уточню: QMap принадлежит только главному потоку... QMap, не является общим для потоков и только главный поток читает данные из него. Но существует дочерний поток, который хочет записать в него данные, потому испускает сигнал с параметрами на слот главного потока где и происходит insert в QMap. Хочу привести начальный пример. Я покаместь опущу использование Q(Multi)Map, по скольку понимаю что проблема заключается в непонимании принципа вызова методов из потока. Очень прошу Вас немного помучаться со мной Существует слот в главном потоке:
существует 1 дочерний поток в котором run() описан следующим образом:
в главном треде инициализирован дочерний поток и описан коннект для него:
Так вот, если в коннекте указать Qt::DirectConnection то согласно QThread::currentThread() выполнение метода произойдет в дочернем потоке, а при Qt::QueuedConnection - в главном. Разве не так? И вот как я понимаю это, есть доступ прямой - когда много потоков вызывают 1 метод не принадлежащий ему, и этот метод выполняет главный поток, и синхронный - когда выполнение метода переносится в другие потоки которые выполняют его синхронно по отношению друг к другу. 2: Задача подразумевает что: QMap принадлежит только(одному) главному потоку, и существует еще 10 потоков, которые дергают метод главного потока сигналом с параметрами. То есть главный поток все время получает параметры на слот и вкладывает их в QMap. Так вот, вопрос в следующем: Вызов методов QMap в ситуации как я описал ранее, в 1 пункте этого поста - в любом случае будет принадлежать чужому потоку?Независимо от того стоит там Qt::DirectConnection или же Qt::DirectConnection, всеравно метод insert для QMap выполнится в чужом потоке? UDP: Я прошу прощения, не ругайте сильно за чушь, если где была такая. Все мы были такими, все чего-то не знали или не понимали. Просто хотелось бы понять что к чему в подробностях, а не по принципу "ну раз работает - значит так надо" Спасибо. Сообщение отредактировал AXELman4ever - 31.10.2011, 20:58 |
|
|
ssoft |
1.11.2011, 8:46
Сообщение
#14
|
Участник Группа: Участник Сообщений: 130 Регистрация: 17.2.2010 Из: Москва Пользователь №: 1470 Спасибо сказали: 30 раз(а) Репутация: 3 |
В такой реализации скрыта серьезная ошибка.
Правда ее допускают вначале практически 95% программистов. Объясняю: Объект QThread - это класс "обертка" вокруг реального потока. Как и любой объект QObject он принадлежит тому потоку, в котором он создан, в данном случае главному потоку. Сигнал
в данном случае опасен, т.к. он принадлежит объекту из главного потока, а вызывается в дочернем. Поэтому здесь Qt::AutoConnection == Qt::DirectConnection и метод объекта главного потока будет вызываться напрямую из дочернего потока. Если написать явно Qt::QueuedConnection, то проблем быть не должно, тюк. метод будет вызываться через очередь. Цитата И вот как я понимаю это, есть доступ прямой - когда много потоков вызывают 1 метод не принадлежащий ему, и этот метод выполняет главный поток, и синхронный - когда выполнение метода переносится в другие потоки которые выполняют его синхронно по отношению друг к другу. Методы не принадлежат потокам). Прямой вызов - это непосредственный вызов слота, как-будто бы метод вызывается явно. Поэтому где он вызван, в том потоке он и выполняется. Разные потоки всегда работают асинхронно, для их синхронизации и используются всякие приблуды типа мьютексов и семафоров. Сделаю еще несколько замечаний: 1. Так писать не нужно, т.к. поток никогда корректно не завершиться.
Нужно либо так
Либо так
2. В инициализации дочернего потока
не указан родитель, т.о. thread никогда не удалиться (будут лики памяти) Можно записать так
3. Здесь очередь не запускается
Очередь запускается только методом exec(). Сообщение отредактировал ssoft - 1.11.2011, 8:48 |
|
|
AXELman4ever |
1.11.2011, 13:33
Сообщение
#15
|
Студент Группа: Участник Сообщений: 31 Регистрация: 22.9.2011 Пользователь №: 2902 Спасибо сказали: 0 раз(а) Репутация: 0 |
Премного благодарен за толковое разъяснение.
1. Что касается замечаний, то пример был состряпан за 5 минут, без какой-либо нужды корректно (или вообще) завершать потоки, потому триггер, регулирующий правильное его завершение я не использовал, но с данным методом я знаком. 2. Тоже самое могу сказать и про отсутствие указателя на родителя в объекте-наследнике QThread, по причине что я не задавался целью удалять объект в процессе работы программы, он ведь сам удалится по её завершению. К тому же, можно исправить ситуацию через delete, ему ведь все равно, есть у объекта родитель или его нет. Спасибо, очень достойное замечание, уверен что я бы сделал ошибку будь у меня иной пример. По 3 замечанию всё понятно Но появилось еще несколько неясностей: Цитата Сигнал
в данном случае опасен, т.к. он принадлежит объекту из главного потока, а вызывается в дочернем. Так именно это мне и требуется. А в чем заключается опасность? Цитата ... Qt::AutoConnection == Qt::DirectConnection и метод объекта главного потока будет вызываться напрямую из дочернего потока. "Метод объекта главного потока будет вызываться напрямую из дочернего потока", а выполняться он будет там же (в дочернем), или он оттуда будет только вызываться, а выполняться будет через очередь? Цитата Прямой вызов - это непосредственный вызов слота, как-будто бы метод вызывается явно. Поэтому где он вызван, в том потоке он и выполняется. А где он вызван? Там, откуда испускается сигнал? А ведь сигнал принадлежит объекту, который в свою очередь принадлежит главному потоку, и согласно выше сказанному, сигнал уже не имеет отношения к дочернему потоку, а может только испускаться из него - это никак не влияет на то, где выполнится метод? Блин, чем глубже я копаю, тем больше сомневаюсь в том, что я хоть что-то понимал Сообщение отредактировал AXELman4ever - 1.11.2011, 14:49 |
|
|
ssoft |
1.11.2011, 16:45
Сообщение
#16
|
Участник Группа: Участник Сообщений: 130 Регистрация: 17.2.2010 Из: Москва Пользователь №: 1470 Спасибо сказали: 30 раз(а) Репутация: 3 |
"Метод объекта главного потока будет вызываться напрямую из дочернего потока", а выполняться он будет там же (в дочернем), или он оттуда будет только вызываться, а выполняться будет через очередь? И выполняться он будет, там же, в дочернем потоке. Где методы вызываются, там они и выполняются. Очереди сообщений и реализованный через них механизм сигнал-слот взаимодействия - это все проделки Qt. А ведь сигнал принадлежит объекту, который в свою очередь принадлежит главному потоку Сигналы и слоты - это обычные методы классов С++. В С++ объекты не принадлежат никаким потокам. Сопоставление QObject конкретному потоку реализовано непосредственно внутри Qt, но перехватывать вызов методов и перенаправлять их в очередь сообщений Qt не умеет. Поэтому в каком потоке вызываем метод - в таком и выполняем (хоть сигнал, хоть слот, хоть обычный метод). А в чем заключается опасность? Опасность в одновременном не синхронизированном доступе к данным из разных потоков, и как следствие возможном нарушения их целостности и крешу программы. |
|
|
AXELman4ever |
1.11.2011, 18:23
Сообщение
#17
|
Студент Группа: Участник Сообщений: 31 Регистрация: 22.9.2011 Пользователь №: 2902 Спасибо сказали: 0 раз(а) Репутация: 0 |
Цитата Опасность в одновременном не синхронизированном доступе к данным из разных потоков, и как следствие возможном нарушения их целостности и крешу программы. Спасибо, я так и предполагал. А синхронизировать доступ можно через выше перечисленные Вами приблуды. Просто требовался подтверждение этому из Ваших "уст". 1: Цитата И выполняться он будет, там же, в дочернем потоке. Где методы вызываются, там они и выполняются. 2: Цитата но перехватывать вызов методов и перенаправлять их в очередь сообщений Qt не умеет. Но ведь (1) справедливо только для прямого вызова (используя Qt::DirectConnection), а если вести речь о вызове через сигнал/слот используя Qt::QueuedConnection? Ведь документация гласит о Qt::QueuedConnection что при таком подключении The slot is executed in the receiver's thread. Как я понимаю это имеет прямое отношение к пункту 2. Об очереди у меня сформировалось такое понятие - что каждый (не прямой) вызов метода из другого потока становится в очередь на выполнение в главном потоке. И когда главный поток берет на себя управление, он пересматривает очередь и выполняет в своем потоке все те задачи на выполнение, которые набежали от дочерних потоков. Если мое понимание не есть верным, очень прошу Вас, докажите мне что это так. Спасибо. Сообщение отредактировал AXELman4ever - 1.11.2011, 18:24 |
|
|
ssoft |
2.11.2011, 17:17
Сообщение
#18
|
Участник Группа: Участник Сообщений: 130 Регистрация: 17.2.2010 Из: Москва Пользователь №: 1470 Спасибо сказали: 30 раз(а) Репутация: 3 |
Начиналось все с того, что
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(). Так что поняли, вроде, как правильно. Просто ранее было написано, что к данным необходим доступ на чтение из дочернего потока. Сообщение отредактировал ssoft - 2.11.2011, 17:18 |
|
|
AXELman4ever |
2.11.2011, 23:13
Сообщение
#19
|
Студент Группа: Участник Сообщений: 31 Регистрация: 22.9.2011 Пользователь №: 2902 Спасибо сказали: 0 раз(а) Репутация: 0 |
Замечательно, вижу свет в конце тоннеля За что, Вам спасибо!
Хочу еще уточнить касательно мьютексов, на своем же примере. Из множества потоков производится вызов метода на запись в экземпляр QMap, и всего 1 поток вызывает метод на их считывание по одному элементу с периодичностью в 1 секунду. После того как запись была изъята - этот элемент удаляется. То есть, если не лочить процесс изъятия элемента из карты, то вызванный метод ненароком может прочесть 1 элемента, а изъять совсем другой? А если элемент не извлекать вовсе, а только считывать его? Каким образом отсутствие мьютекса, в таком случае, может вызвать краш? Сообщение отредактировал AXELman4ever - 3.11.2011, 0:24 |
|
|
ssoft |
3.11.2011, 7:45
Сообщение
#20
|
Участник Группа: Участник Сообщений: 130 Регистрация: 17.2.2010 Из: Москва Пользователь №: 1470 Спасибо сказали: 30 раз(а) Репутация: 3 |
То есть, если не лочить процесс изъятия элемента из карты, то вызванный метод ненароком может прочесть 1 элемента, а изъять совсем другой? Верно абсолютно. А если элемент не извлекать вовсе, а только считывать его? Каким образом отсутствие мьютекса, в таком случае, может вызвать краш? Предположим поток 1 записывает данные, а поток 2 считывает. Запись данных приводит к вызову метода, внутри которого изменяются сами данные и структура их хранения. Например, в реализации Qt для QMap сначала физически удаляются данные (вызывается деструктор), связанные с элементом, а затем правится структура QMap. Так вот, без мьютекса, если метод чтения потока 2 будет вызван после удаления данных потоком 1, но до изменения структуры данных потоком 1, то это и может привести к крешу. |
|
|
Текстовая версия | Сейчас: 22.11.2024, 7:22 |