In der nächsten Developer-Version von VDR werden SVDRP-Befehle in einem separaten Thread ausgeführt, was zu teilweise erheblich besseren Response-Zeiten führt. Der Zugriff auf globale Datenstrukturen wie etwa Timer, Channels etc. muß dafür allerdings streng reglementiert werden, damit nicht zwei verschiedene Threads gleichzeitig schreibend zugreifen. Das bisherige BeingEdited() reicht dafür nicht mehr.
Nun überlege ich seit einigen Tagen, wie ich das am besten angehe. Dabei gibt es aus momentaner Sicht zwei Möglichkeiten (am Beispiel der Timer):
- Es bleibt im Wesentlichen alles, wie es ist, also weiterhin eine globale Variable 'Timers', auf die einfach zugegriffen werden kann. cTimers bekommt entsprechende Lock() und Unlock() Funktionen, aber ob die wirklich aufgerufen werden ist Sache des Entwicklers. Es wird auch nichts dagegen unternommen, daß man zwar einen Read-Lock() hat, aber dennoch schreibend zugreift.
- Die gobale Variable 'Timers' wird "versteckt" und ist nur noch über Zugriffsfunktionen erreichbar, die zum Einen für das Locking sorgen und zum anderen auch im Falle eines Read-Locks die Liste nur als 'const' zurückgeben.
Die erste Variante hätte den Vorteil, daß an Plugin-Code nur wenig geändert werden müsste. Es bräuchten lediglich an den entsprechenden Stellen die Lock()/Unlock()-Aufrufe eingebaut zu werden. Der Nachteil dabei ist, daß nicht garantiert ist, daß alle Stellen richtig gelockt werden, und auch bei neu zu schreibendem Code wird man nicht automatisch dazu verpflichtet, entsprechend zu locken.
Die zweite Variante hätte den Vorteil, daß ein Zugriff ohne korrektes Locking gar nicht mehr möglich wäre. Alle vorhandenen Zugriffstellen würden automatisch vom Compiler erkannt, da die globale Variabe 'Timers' nicht mehr existiert. Nachteil wäre, daß in allen Plugins, die mit Timern arbeiten, etwas umfangreichere Änderungen nötig wären, da z.B. nicht mehr über 'Timers.Xyz()' zugegriffen wird, sondern über 'Timers->Xyz()'. Aber auch das würde alles der Compiler melden.
Für die zweite Variante wäre es wohl praktisch, das Locking und die State-Abfrage (d.h. ob sich die Liste seit dem letzten lesenden Zugriff verändert hat) in einem Aufwasch zu machen. Ein mögliches Interface hierzu könnte so aussehen:
static const cTimers *GetTimersRead(cStateKey &StateKey);
///< Gets the list of timers for read access. The list is locked and
///< a pointer to it is returned if the state of the list is different
///< than the state of the given StateKey. If both states are equal,
///< the list of timers has not been modified since the last call with
///< the same StateKey, and NULL will be returned (and the list is not
///< locked). After the returned list of timers is no longer needed,
///< the StateKey's Remove() function must be called to release the
///< list. The time between calling cTimers::GetTimersRead() and
///< StateKey.Remove() should be as short as possible.
///< After calling StateKey.Remove() the list returned from this call
///< must not be accessed any more. If you need to access the timers
///< again later, a new call to GetTimersRead() must be made.
///< A typical code sequence would look like this:
///< cStateKey StateKey;
///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
///< // access the timers
///< StateKey.Remove();
///< }
static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of timers for write access. If TimeoutMs is given,
///< it will wait that long to get a write lock before giving up.
///< Otherwise it will wait indefinitely. If no write lock can be
///< obtained within the given timeout, NULL will be returned.
///< If a write lock can be obtained, the list of timers will be
///< returned, regardless of the state values of the timers or the
///< given StateKey.
///< After the returned list of timers is no longer needed,
///< the StateKey's Remove() function must be called to release the
///< list. The time between calling cTimers::GetTimersWrite() and
///< StateKey.Remove() should be as short as possible.
///< After calling StateKey.Remove() the list returned from this call
///< must not be accessed any more. If you need to access the timers
///< again later, a new call to GetTimersWrite() must be made.
///< The call to StateKey.Remove() will increment the state of the
///< list of timers and will copy the new state value to the StateKey.
///< You can suppress this by using 'false' as the parameter to the
///< call, in which case the state values are left untouched.
///< A typical code sequence would look like this:
///< cStateKey StateKey;
///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
///< // access the timers
///< StateKey.Remove();
///< }
Alles anzeigen
Da ich da jetzt irgendwie hin- und hergerissen bin zwischen "quick&dirty" und einer wohl stabileren, aber aufwändigeren Lösung, würde ich gerne mal ein Paar Meinungen dazu hören. Vielleicht kommt ja auch jemand noch auf eine ganz andere Idee ;-).
Klaus