| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C++ Builder > VCL и многопоточность |
| Автор: _Tanatos_ 30.11.2007, 01:50 | ||
| Помогите решить задачку: Есть класс отвечающий за ведение журнала (лога). Работает он следующим образом: - в процессе инициализации класса ему передаются указатели на объекты "подписчики" - когда вызывается функция регистрации нового сообщения поочередно вызывается функция для каждого подписчика. В эту функцию передается ссылка на структуру с данными сообщения. В моем случае имеется один подписчик (класс TForm, для этого он наследуется он класса "подписчика"). Функция OnLogAdd(...) соответственно вызывается при регистрации нового сообщения в журнале. Ее задача отображать на экране все сообщения (аля консоль). Для отображения я использую TTree. В программе могут стартовать и останавливаться потоки выполняющие некоторые вычисления. И основной поток и вспомогательные ведут журнал при помощи глобального объекта (того самого класса журнала). Сам класс вродебы потокобезопасный, во всяком случае с его стороны глюков не ловил. Проблема заключается в том, что программа периодически зависает. Причиной зависаний 99% работа с TTree, во всяком случае закоментировав работу с ним я не поймал ни одного зависания. Помогите как организовать потокобезопасный вывод? Попробовал такой вариант: в классе TForm написал функцию void Update() в которую вынес все что касается обновления дерева с журналом. в функцию OnLogAdd(...) поместил только сохранение копии сообщения (которая далее используется в Update() В потоке, после записи сообщения в журнал, принудительно вызываю Synchronize(Form1->Update); Так же после записи сообщения в журнал в основном потоке вызываю Update() Все отлично работает за исключением того, что пихать повсюду Update() ИМХО некорректно. И потом нет гарантий, что пока будет вызываться Update не будет перезаписана копия сообщения, т.е. реально можно просто потерять сообщение в журнале. Может я немного путанно написал, ежели что непонятно, спрашивайте. Мне очень важно найти решение этой задачи! Надеюсь на Вашу помощь!
|
| Автор: Artemon 30.11.2007, 11:06 |
| 1. Если обращаешься к визуальным компонентам VCL, то обязательно нужно использовать Synchronize, что ты и сделал и от этого никуда не уйти. 2. Я так понял у тебя один объект и его могут менять все, на этот случай можно воспользоваться мнтодами синхронизации, например Mutex или Семафор. |
| Автор: Lazin 30.11.2007, 12:24 |
| без Synchronize никуда, так как компоненты VCL используют очередь сообщений |
| Автор: _Tanatos_ 30.11.2007, 13:01 | ||||
| Не имею ничего против Synchronize, но как его грамотно использовать в моем случе? Вот фрагмент исходного кода:
В результате
Причем нет гарантии, что реальный вызов не будет таким: main_thread LogHelper.Add() << "ThreadTest Main" << loPost; thread_1 LogHelper.Add() << "ThreadTest sub" << loPost; thread_1 Synchronize(Form1->Update); main_thread Update(); в результате получим две одинаковые строчки: 12 "ThreadTest sub" 12 "ThreadTest sub" вместо правильных 11 "ThreadTest Main" 12 "ThreadTest sub" |
| Автор: xvr 30.11.2007, 13:35 | ||
Тебе нужна очередь в классе логера, в которую будут складываться сообщения от источников. Доступ к очереди должен быть синхронизирован. Порядок действий (функция OnLogAdd): 1) Блокируется очередь (критической секцией или семафором, неважно) 2) Сообщение добавляется в конец очереди 3) Разблокируется очередь 4) Вызывается Synchronize(Stage2) Фуннкция Stage2 (в классе логера): 1) Блокируется очередь 2) Из очереди вычитываются ВСЕ содержащиеся в ней сообщения, для каждого вызывается callback от подписчиков. (после этого очередь пуста) 3) Разблокируется очередь Для очереди можно применить класс deque из stl |
| Автор: xvr 30.11.2007, 14:58 | ||||||||||
Пардон, я имел в виду функцию по добавлению записи в лог.
Это и есть одно из 2х главных ее отличий.
Это второе отличие - она должна САМА вызывать эту функцию обновления (п. 4)
Поместите весь логер в отдельный поток и вызывайте наздоровье Или скопируйте реализацию метода Synchronize из TThread к себе в логер. |
| Автор: _Tanatos_ 30.11.2007, 15:43 | ||
Интересно, где ты видел реализацию этой функции? А подвязывать класс журнала на VCL однозначно неправильно!!!, так как этот класс должен быть независим ни от каких библиотек кроме std Добавлено через 3 минуты и 4 секунды ИМХО Класс журнала должен безопасно регистрировать новое сообщение (пусть будет очередь сообщений) Затем от должен поочередно безопасно вызвать функци OnLogAdd Затем уже в пределах функции OnLogAdd должно быть выполнено безопасное действие про сохранению, отображению и т.д. сообщения. Вопрос в том, что внутри метода Synchronize, может буть это можно сделать самомму в пределах функции OnLogAdd() ? |
| Автор: xvr 30.11.2007, 18:31 | ||||||||||||
В исходниках vcl
Тогда его клиенты должны быть готовы к тому, что их будут вызывать на любых потоках, к чему vcl не готов.
Это зависит от того, сколько будет этих OnLogAdd. Если придется делать много разновидностей OnLogAdd с синхронизацией, то лучше ее поместить в сам логер, или сделать какой-нибдь wrapper для синхронизации
Посмотри сам в файле Source\vcl\classes.pas Собственно можно сделать и проще (так было сделано в BCB 3) - Synchronize посылала спец. сообщение в handle Application, по которому та в оконной процедуре вызывала запрошенную процедуру (теперь спец. сообщения нет, а производится переодический контроль на сообщении WM_NULL) (от BCB 6.0)
|