Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

11

01.08.2011, 12:46

Hab heute mal einen Kollegen auf der Arbeit gefragt, er sagte der C++ Standard beinhaltet nichts zum Thema multithreading.
Java sei einer der wenigen Programmiersprachen, die von vorn herein für multithreading designed wurden.
Somit sind die STL Container alle nicht thread sicher.
Allerdings ist das in C++ kein Problem, solange nicht unterschiedliche threads den selben Speicherberich beschreiben.
Deshalb habe ich bis dato mit den STL Containern in Bezug auf MT keine Probleme gehabt.

Momentan stürzt mein Socket Programm eben nur ab, wenn einer im Netzwerk disconnected. Das sollte sich aber auch in den Griff kriegen lassen.

Gruß Lukas :-)

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

12

01.08.2011, 17:04

"Allerdings ist das in C++ kein Problem, solange nicht unterschiedliche threads den selben Speicherberich beschreiben."
Leider auch nicht richtig. Siehe:
http://msdn.microsoft.com/en-us/library/…b(v=vs.80).aspx
Oder kürzer:
"multiple write" -> unsicher
"multiple read, one write" -> unsicher
"multilpe read, no write" -> sicher
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

13

02.08.2011, 10:08

Gut mag sein, threading programmierung ist und bleibt eben schwierig. Notfalls baue ich eben noch überall locks/unlocks usw. ein.
Im Moment läuft mein NetzwerkSystem jeden Falls wunderbar. Ich kanns ja heute irgend mal hochladen. Habe das hauptsächlich getestet durch mehrmaliges starten und dann eben immer auf 127.0.0.1 connected.
Vielleicht bekommt ihr das ja eher zum Absturz ^^ - ist ein kleines Chat Programm.

EDIT:

Wer mit der Klasse was anfangen kann, viel Spaß damit ^^

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <list>
#include <vector>

#if defined(_WIN32)
#   include <windows.h>
#endif


enum EContainerLocks
{
    CONTAINERLOCK_NONE,
    CONTAINERLOCK_READ,
    CONTAINERLOCK_WRITE,
};


template < template < class T, class Allocator = std::allocator<T> > class C > class LockableContainer
{
    
    public:
        
        LockableContainer() : Lock_(CONTAINERLOCK_NONE), LockCount_(0)
        {
        }
        LockableContainer(const C &other) : List(other), Lock_(CONTAINERLOCK_NONE), LockCount_(0)
        {
        }
        ~LockableContainer()
        {
        }
        
        /* Functions */
        
        void lock(const EContainerLocks Lock)
        {
            if (Lock != CONTAINERLOCK_NONE)
            {
                /* Wait until the list is unlocked */
                if (Lock == CONTAINERLOCK_WRITE)
                {
                    while (Locked_ != CONTAINERLOCK_NONE)
                        yield();
                }
                else if (Lock == CONTAINERLOCK_READ)
                {
                    while (Locked_ == CONTAINERLOCK_WRITE)
                        yield();
                }
                
                /* Set new lock mode and increment lock count */
                Locked_ = Lock;
                ++LockCount_;
            }
        }
        
        void unlock()
        {
            if (LockCount_)
            {
                --LockCount_;
                if (!LockCount_)
                    Locked_ = CONTAINERLOCK_NONE;
            }
        }
        
        /* Inline functions */
        
        inline EContainerLocks mode() const
        {
            return Locked_;
        }
        
        inline bool locked() const
        {
            return Locked_ == CONTAINERLOCK_WRITE;
        }
        
        /* Members */
        
        C List;
        
    private:
        
        /* Functions */
        
        inline void yield()
        {
            #if defined(_WIN32)
            Sleep(1);
            #elif defined(__linux__)
            usleep(1000);
            #endif
        }
        
        /* Members */
        
        EContainerLocks Lock_;
        unsigned int LockCount_;
        
};

template < class T, class Allocator = std::allocator<T> > typedef LockableContainer< std::list<T, Allocator> > LockableList;
template < class T, class Allocator = std::allocator<T> > typedef LockableContainer< std::vector<T, Allocator> > LockableVector;

/*
Example of how to use:

LockableList<int> MyList;

MyList.lock(CONTAINERLOCK_WRITE); // <- waits here until the list is ready to be written

MyList.List.push_back(rand() % 100);
MyList.List.push_back(rand() % 100);
MyList.List.push_back(rand() % 100);

MyList.unlock();
*/

PS: Beware of bugs in the above code; I have only proved it correct, not tried it. ;-)

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »LukasBanana« (02.08.2011, 10:49)


Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

14

02.08.2011, 18:46

Uahhh *wegrenn*

Sorry aber sowas ist russisch Rolette und dazu noch wenig performant. Es gibt gute Gründe warum es CS bzw mutexe gibt. Diese sollten auch genutzt werden. Wenn du dennoch selbst über eine Zustandsvariable das ganze implementieren willst, dann deklarier die Zustandvariable als "volatile" und arbeite mit atomic_ops.

EDIT: pardon ich sagte, nur dass man es nicht nutzen sollte, aber nicht wieso; Das Problem ist, dass bei deiner Variante immernoch zwei Threads den gleichen Container locken können, da du eine Variable prüfst und dann setzt. Aber zwischen diesen beiden Schritten kann es zu einem Threadswitch kommen (dafür gibt es die atomare "compare_and_swap" Funktion). Außerdem ist es dem Kompiler möglich den Wert der Variable in den Registern zu lassen ohne auf einen neuen Wert im Speicher zu prüfen (dafür ist das Zauberwort volatile zuständig um solche Optimierungen zu vermeiden).
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

15

03.08.2011, 10:47

Also hier ist die Rede, dass Mutex eher für mehrere Anwendungen und nicht für innerhalb einer Andwendung mit mehreren Threads gedacht ist.
Aber hier mal meine überarbeitete Variante:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Lockable list

#include <list>
#include <vector>

#if defined(_WIN32)
#   include <windows.h>
#endif


template < template < class T, class Allocator = std::allocator<T> > class C > class LockableContainer
{
    
    public:
        
        LockableContainer()
        {
            createMutex();
        }
        LockableContainer(const C &other) : List(other)
        {
            createMutex();
        }
        ~LockableContainer()
        {
        }
        
        /* Functions */
        
        void lock(const EContainerLocks Lock)
        {
            /* Wait until the list is unlocked */
            while (WaitForSingleObject(Mutex_, INFINITE) == WAIT_OBJECT_0)
                yield();
        }
        
        void unlock()
        {
            /* Unlock list -> release mutex */
            ReleaseMutex(Mutex_);
        }
        
        /* Members */
        
        C List;
        
    private:
        
        /* Functions */
        
        inline void yield()
        {
            #if defined(_WIN32)
            Sleep(1);
            #elif defined(__linux__)
            usleep(1000);
            #endif
        }
        
        inline void createMutex()
        {
            Mutex_ = CreateMutex(NULL, FALSE, "Mutex1");
        }
        
        /* Members */
        
        HANDLE Mutex_;
        
};

template < class T, class Allocator = std::allocator<T> > typedef LockableContainer< std::list<T, Allocator> > LockableList;
template < class T, class Allocator = std::allocator<T> > typedef LockableContainer< std::vector<T, Allocator> > LockableVector;

Mh, ist irgendwie deutlich weniger geworden ^^. Mit welchen WinAPI Funktionen könnte man das denn performanter machen als mit Mutex?

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

16

03.08.2011, 11:21

Bei windows nutzt man critical sections. Unter linux halt mutex bzw. nutze ich da pthread_mutexattr_t.

EDIT: auszug aus meiner Portable.h

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifdef _WIN32
class MY_CRITICALSECTION
{
    mutable CRITICAL_SECTION critcal;

    void operator = (const MY_CRITICALSECTION&) {}
public:
    MY_CRITICALSECTION(const MY_CRITICALSECTION&)   { InitializeCriticalSection(&critcal); }
    MY_CRITICALSECTION(void)                        { InitializeCriticalSection(&critcal); }
    ~MY_CRITICALSECTION(void)                       { DeleteCriticalSection(&critcal); }

    void Lock(void) const       { EnterCriticalSection(&critcal); }
    void Unlock(void) const     { LeaveCriticalSection(&critcal); }
};

#else
class MY_CRITICALSECTION
{
    mutable pthread_mutex_t critcal;

    void operator = (const MY_CRITICALSECTION&) {}

    void create(void)
    {
        pthread_mutexattr_t attr;

        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

        pthread_mutex_init(&critcal, &attr);
    }

public:
    MY_CRITICALSECTION(const MY_CRITICALSECTION&)   { create(); }
    MY_CRITICALSECTION(void)                        { create(); }

    ~MY_CRITICALSECTION(void)   { pthread_mutex_destroy(&critcal); }
    void Lock(void) const       { pthread_mutex_lock(&critcal); }
    void Unlock(void) const     { pthread_mutex_unlock(&critcal); }
};

Nutzung für "sichere Liste":

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#pragma once

#ifndef DONT_INCLUDE_HEADERS
#include "Portable.h"
#include <list>
#endif


namespace helpers
{
//!threadsafe (but not exceptionsafe!) std::list
template <class T> class SecureList : private std::list<T>
{
    MY_CRITICALSECTION Critic;

    void operator = (const SecureList&)     { }
public:
    typedef typename std::list<T>       p;
    typedef typename p::iterator        iterator;
    typedef typename p::const_iterator  citerator;
    typedef typename p::size_type       size_type;

    SecureList(const p &c)                  { p::insert(begin(true), c.begin(), c.end()); unlock(); }
    SecureList(const SecureList &c) : p()   { p::insert(begin(true), c.begin(true), c.end()); c.unlock(); unlock(); }
    SecureList(void)                        { }
    ~SecureList()                           { }

    //! calls lock of the criticalsection /mutex
    void lock(void) const                       { Critic.Lock(); }
    //! calls unlock of the criticalsection /mutex
    void unlock(void) const                     { Critic.Unlock(); }


    //! calls lock, std::list::begin and returns the result
    citerator begin(bool lockit) const                  { if(lockit) lock(); return p::begin(); }
    //!calls std::list::end and returns the result
    citerator end(void) const                           { return p::end(); }
    //! calls lock, std::list::begin and returns the result
    iterator begin(bool lockit)                         { if(lockit) lock(); return p::begin(); }
    //!calls std::list::end and returns the result
    iterator end(void)                                  { return p::end(); }
    //!calls std::list::erase. Container should be locked before
    iterator erase(iterator it)                         { return p::erase(it); }
    //! calls lock, std::list::clear and unlock
    void clear(bool lockit)                             { if(lockit) lock(); p::clear();                                        if(lockit) unlock(); }
    //! calls lock, std::list::push_back and unlock
    void push_back(const T& e, bool lockit)             { if(lockit) lock(); p::push_back(e);                                   if(lockit) unlock(); }
    //! calls lock, std::list::push_front and unlock
    void push_front(const T& e, bool lockit)            { if(lockit) lock(); p::push_front(e);                                  if(lockit) unlock(); }
    //! calls lock, std::list::remove and unlock
    void remove(const T& e, bool lockit)                { if(lockit) lock(); p::remove(e);                                      if(lockit) unlock(); }
    //! calls lock, std::list::sort and unlock
    void sort(bool lockit)                              { if(lockit) lock(); p::sort();                                         if(lockit) unlock(); }
    //! calls lock, std::list::unique and unlock
    void unique(bool lockit)                            { if(lockit) lock(); p::unique();                                       if(lockit) unlock(); }
    //! calls lock, std::list::splice and unlock
    void spliceIn(p &list, bool lockit)                 { if(lockit) lock(); list.splice(list.begin(), *this);                  if(lockit) unlock(); }
    //! calls lock, std::list::merge and unlock
    void merge(p &list, bool lockit)                    { if(lockit) lock(); p::merge(list);                                    if(lockit) unlock(); }
    //! calls lock for both, std::list::splice and unlock
    void splice(SecureList<T> &list, bool lockit)       { if(lockit) { lock(); list.lock(); } p::splice(begin(false), list);    if(lockit) { list.unlock(); unlock(); } }
    //! calls lock for both, std::list::merge and unlock
    void merge(SecureList<T> &list, bool lockit)        { if(lockit) { lock(); list.lock(); } p::merge(list);                   if(lockit) { list.unlock(); unlock(); } }
    //! calls std::list::size and returns the result
    size_type size(void) const                          { return p::size(); }
    //! calls std::list::empty and returns the result
    bool empty(void) const                              { return p::empty(); }


    //template<class _Iter> void insert(iterator _Where, _Iter _First, _Iter _Last)
    //{
    //  std::list<T>::insert(_Where, _First, _Last);
    //}
    //! calls lock, std::list::insert and returns the result
    iterator insert(iterator _Where, const T& _Val, bool lockit)
    {
        if(lockit) lock();
        return p::insert(_Where, _Val);
    }
};

};

Diese "sichere Liste" ist halt so leidlich gut implementiert. Besser wäre es die iteratoren zu überladen, wenn dies möglich ist. Ggf würde das sogar kürzer und viel besser sein, allerdings fehlt mir dafür das wissen, wie ich der std::list eine andere iterator Klasse unterjubeln kann.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Nox« (03.08.2011, 11:31)


LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

17

03.08.2011, 11:42

Das ist doch mal hilfreich, danke :-)

Aber ist es wirklich vernünftig von std::list abzuleiten? Immerhin hat diese Klasse keinen virtuellen Destruktor. Ich dachte immer von solchen Klassen soll man nicht ableiten.
Allerdings weiß ich auch nicht was "privates" ableiten bringt, vlt. geht das ja damit?! Kann mich da bitte jemand aufklären!?

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

18

03.08.2011, 13:01

Privates Ableiten verhindert jede Möglichkeit die Original-Methoden der Oberklasse aufzurufen. Ist wichtig, weil er ja keine Methoden überschreibt, sondern mit einem zusätzlichen Parameter erweitert.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

19

03.08.2011, 13:48

Ok, also sollen von Klassen ohne virtuellen Destruktor keine Funktionen überschrieben werden?

Wäre folgender Code dann trotzdem noch zulässig?

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
template <typename T> class MyList : private std::list<T>
{
    /* ... */
    
    void clear(bool lockit = true) // MIT DEFAULT WERT
    {
        std::list<T>::clear();
    }
    
    /* ... */
}

Werbeanzeige