Pulizia della memoria non effettuata

di il
10 risposte

Pulizia della memoria non effettuata

Salve ragazzi!
Sto utilizzando c++11, sfml, threads e liste STL sempre del c++.
Sto programmando un server per un videogame, e il thread del game ha qualcosa che non va. Dal momento che deve essere sincronizzato con altri thread ho utilizzato il sistema con "snapshot", ossia una lista di stati del game, ognuno contenente tutte le variabili del gioco. Questo snapshot è creato ad ogni ciclo, e in ogni ciclo si eliminano gli snapshot in eccesso (max 100 stati, a meno che un thread non stia usando quello snapshot: in quel caso aspetta che finisca di utilizzarlo prima di cancellarlo con pop_back().)
Ogni volta che faccio partire questo thread, che sarebbe il thread del game la memoria utilizzata cresce di 1 mb al secondo:

void game(int id)
{
    Clock clock;
    Time time;
    bool gameOn = true;
    int i = 0, j = 0;
    ServerStatus *currentStatus; //Lo status snapshot che verrà creato dopo che il ciclo finisce, e aggiunto con push_front() alla lista di snapshot
    list<Ship>::iterator ship; //L'iteratore per lista di navi (che è una lista globale)
    list<ServerStatus>::iterator StatusItr;
    time = clock.getElapsedTime();
    double timePassed = 0;
    
    
    while(gameOn) //praticamente un while true
    {
        timePassed = time.asMicroseconds();
        time = clock.getElapsedTime();
        timePassed = time.asMicroseconds() - timePassed; //Gestione del tempo necessario ai frame del gioco
        while(i < MAX_CLIENTS)
        {
            if(client[i].status == "Waiting game")
            {
                client[i].status = "Initializing";
                thread joinGame(prepareClient, i); //Launch di un altro thread, viene eseguito solo quando un nuovo client si connette
                joinGame.detach();
            }
            i++;
        }
        i = 0;

        ship = ships.begin();//Inizio lettura navi
        j = 0;
        while(ship != ships.end()) 
        {
            	//Calcolo dati del game... (per ora sono solo le posizioni e rotazioni aggiornate delle navi
        }
        currentStatus = new ServerStatus(ships);  //Creazione dello snapshot e inserimento lista delle navi
        serverStatus.push_front(*currentStatus);  //Aggiunta snapshot alla lista degli stati (e quindi snapshot)

        while(serverStatus.size() > 100) //Cancellazione degli snapshot in eccesso
        {
            StatusItr = serverStatus.end();
            StatusItr--;
            //cout <<StatusItr->usedBy<<endl;

            if (StatusItr->nThreads->size() == 0) /*Per vedere se un thread sta usando uno snapshot uso una lista all'interno di esso, il thread fà un push_back() quando inizia a usarlo e un pop_back() quando smette. Per ora funziona, ma per la versione ufficiale utilizzerò le variabili atomiche. Appena avrò tempo di leggermi 300 pagine di "concurrency in c++" :P */
                 serverStatus.pop_back();

        }

        cout <<serverStatus.size()<<endl; //Il risultato è sempre 100. Non è ancora successo che un thread rimanga indietro di 100 snapshot e quindi rallenti il server.
    } //End main loop
}

Fine.
Tralasciando lo stile (è solo un prototipo, ci sono attributi pubblici, è poco commentato ecc), dove sto sovraccaricando la memoria?? Cosa sto facendo di sbagliato? Il pop_back() dovrebbe fare tutto...



EDIT: Ho trovato l'errore neanche un minuto dopo aver postato sul forum giuro, ero ore cercando l'errore...
currentStatus = new ServerStatus(ships);
Questo crea un nuovo oggetto ogni volta. E mi ero dimenticato di distruggere il precedente.

EDIT 2:
Con "delete currentStatus;" non cambia niente...

10 Risposte

  • Re: Pulizia della memoria non effettuata

    Non credo risolva il problema, ma questa operazione è inutile per come l'hai scritta (a prescindere dalla delete che hai aggiunto)
    
            currentStatus = new ServerStatus(ships);  //Creazione dello snapshot e inserimento lista delle navi
            serverStatus.push_front(*currentStatus);  //Aggiunta snapshot alla lista degli stati (e quindi snapshot)
    
    tanto vale scrivere direttamente
    
            serverStatus.push_front(currentStatus(ships));  //Aggiunta snapshot alla lista degli stati (e quindi snapshot)
    
    o meglio ancora
    
            serverStatus.emplace_front(ships);  //Aggiunta snapshot alla lista degli stati (e quindi snapshot)
    
    E occhio che la STL non è garantita essere multithreading safe (ossia al 99.99% non lo è).
  • Re: Pulizia della memoria non effettuata

    Sinceramente non ho idea di cosa voglia dire essere multithread safe per una stl :c ho iniziato il mondo del multithreading da poco.
    L'accesso alla lista ecc. l'ho gestita io, quindi non credo che sia quella il problema...
    p.s. ho scritto esattamente quello che hai messo (serverStatus.emplace_front(ships)), il risultato non cambia :C

    In un post di un altro forum ho letto che probabilmente è l'os (Windows 10 nel mio caso) che continua a dare la memoria che ha utilizzato, senza aggiornare quella che ha liberato. E' possibile?
  • Re: Pulizia della memoria non effettuata

    n un post di un altro forum ho letto che probabilmente è l'os (Windows 10 nel mio caso) che continua a dare la memoria che ha utilizzato, senza aggiornare quella che ha liberato. E' possibile?
    Si, l'OS è libero di tenere in stand by quella memoria in modo da essere immediatamente riutilizzata nel caso il processo la richieda. Tu quello che devi garantire è che le allocazioni che fai, prima o poi le liberi; il resto spetta all'allocatore di sistema.
    p.s. ho scritto esattamente quello che hai messo (serverStatus.emplace_front(ships)), il risultato non cambia :C
    Infatti non ho detto che lo risolveva. Comunque (in teoria) dovrebbe essere più veloce in quanto eviti costruzioni di temporanei inutili, costruendo l'oggetto direttamente dentro la lista.
    Sinceramente non ho idea di cosa voglia dire essere multithread safe per una stl
    Semplicemente che solo e soltanto gli accessi in sola lettura alle variabili (ossia a quelle che mai e poi mai modificherai nel corso del programma) sono sicuri. Se solo un accesso tra i millemila che puoi fare avviene in scrittura (quindi modificando), tutti i millemila +1 accessi che farai a quella variabile devono essere protetti da mutex (documentati su race condition).
    
    list<Ship>::iterator ship; //L'iteratore per lista di navi (che è una lista globale)
    
    In questo caso se nei thread utilizzi la lista ship è probabile doverne regolare l'accesso tramite mutex
  • Re: Pulizia della memoria non effettuata

    Non utilizzo mutex, utilizzo un algoritmo wait-free. E' accennato (e quindi non spiegato bene) sopra. E' una scelta funzionale: con un thread per client (questa cosa verrà abbassata a un thread per X clients, quando capirò l'i/o asincrono) bloccare la variabile con un mutex è totalmente inutile: tanto vale mettere la gestione dell'i/o di ogni client in un while nel thread del game. Il mutex in questo caso elimirerebbe solo il vantaggio offerto dal multithreading.
    P.s. la lista credo sia thread safe, da come la definisci tu: dopo la creazione del nodo, il nodo viene usato in sola lettura prima di essere cancellato. E la cancellazione avviene soltanto se nessun thread sta leggendo la lista in quel momento (con la tecnica spiegata prima).


    Comunque, se l'os riserva la memoria per futuri utilizzi, perchè quando ricrea l'oggetto non usa quella appena cancellata ma il counter della memoria appena utilizzata continua ad aumentare lo stesso?
  • Re: Pulizia della memoria non effettuata

    Non so che mutex tu intenda (presumo un kernel mutex), ma anche uno spin lock è un mutex (uno user mutex)
    http://en.cppreference.com/w/cpp/atomic/atomic_fla
    comunque non avendo il quadro complessivo non insisto oltre.
    Comunque, se l'os riserva la memoria per futuri utilizzi, perchè quando ricrea l'oggetto non usa quella appena cancellata ma il counter della memoria appena utilizzata continua ad aumentare lo stesso?
    Dovresti chiederlo a chi ha scritto l'allocatore di memoria di Windows, non a me. Dovresti anche vedere se si stabilizza o continua a crescere all'infinito. Può anche darsi che l'allocatore decida di posporre il riuso della memoria perché la trova ancora allocata a livello di flag interni.
    Vallo a sapere...
  • Re: Pulizia della memoria non effettuata

    Continua a crescere all'infinito...
  • Re: Pulizia della memoria non effettuata

    La funzione prepareClient come l'hai definita?
    Cha dati globali condividi con void game(int id)?
  • Re: Pulizia della memoria non effettuata

    I dati globali che condivido sono:

    list<Ship> ships;
    list<ServerStatus> serverStatus;
    Client client[MAX_CLIENTS]; //La mia classe

    Gli attributi di ServerStaus sono:
    list<unsigned> *nThreads;
    list<Ship> *ships;

    PrepareClient è come un thread handshake. Si connette con il client e tramite socket si mette d'accordo per il nickname e il tipo di nave che vuole essere.
    Ho controllato più volte lo spawn di quel thread, viene eseguito correttamente solo quando un client si connette.
    Inoltre la memoria inizia ad aumentare dal momento che dichiaro l'oggetto, che faccia il push_front o meno (ora sono tutti nella stessa riga)... Quindi vedo molto difficile che il problema sia in altri thread.
  • Re: Pulizia della memoria non effettuata

    L'unica cosa scontata col multithreading sono le rogne che si porta dietro.
    Il problema può nascondersi nel fatto che i dati condivisi non sono protetti (le std::list allocano memoria per le strutture interne), e il fatto che il thread sia detached complica le cose.
    Dovresti mostrare il codice della prepareClient (e/o) dei thread che usano le variabili globali, altrimenti non si può dire nulla di più di quanto detto.
  • Re: Pulizia della memoria non effettuata

    Avrei preferito non farlo, dato che le altre funzioni sono scritte ancora peggio del game. Ma se sostieni che non ci sia altro da fare...
    
    bool prepareClient(int id)
    {
        bool done = true;
        bool playerFound = false;
        char buffer[MAX_BUFFER];
        Socket::Status sstatus = Socket::Done;
        size_t receivedBytes;
        string stringBuffer;
        string data[50];
        stringstream ss;
        int i = 0;
    
        if(client[id].type == "helm")
        {
            client[id].status = "Looking for weapons";
            stringBuffer = "WAIT";
            memset(buffer, '\0', MAX_BUFFER);
            strcpy(buffer, stringBuffer.c_str());
    
    
            if(sstatus == Socket::Done)
            {
                while(client[id].mate == "no_mate" && done)
                {
                    sleep(milliseconds(100));
                    sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                    if(sstatus != Socket::Done and sstatus != Socket::NotReady)
                        done = false;
                }
                if (done == false)
                {
                    cout <<"Error: client " <<id <<" disconnected." <<endl;
                    client[id].clean();
                }
                if(client[id].mate != "no_mate")
                {
                    cout <<client[id].nickname <<" connected with " <<client[id].mate <<endl;
                    client[id].status = "In Game";
                    ss.str(string());
                    ss << id;
    
                    stringBuffer = "IN_GAME|" + ss.str();
                    Ship ship(id, client[id].mateid, "basic");
                    ships.push_back(ship);
                    //client[id].helm.initialize(0, 0, id, "basic", 100, 0);
                    memset(buffer, '\0', MAX_BUFFER);
                    strcpy(buffer, stringBuffer.c_str());
                    sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                    thread communicate(communicationThread, id);
                    communicate.detach();
                }
            }
            if (sstatus != Socket::Done && done == true)
            {
                cout <<"Error: client " <<id <<" disconnected." <<endl;
                client[id].clean();
                done = false;
            }
        }
        else
        {
            while(done == true and client[id].status != "In Game")
            {
    
                stringBuffer = "SELECT_NICKNAMES";
                i = 0;
                while(i < MAX_CLIENTS)
                {
                    if(client[i].status == "Looking for weapons")
                    {
                        stringBuffer = stringBuffer + "|" + client[i].nickname;
                    }
                    i++;
                }
                memset(buffer, '\0', MAX_BUFFER);
                strcpy(buffer, stringBuffer.c_str());
                sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                if(sstatus == Socket::Done)
                {
                    sstatus = client[id].socket.receive(buffer, MAX_BUFFER, receivedBytes);
                    cout <<sizeof(buffer)<< "   "<< receivedBytes <<endl;
                    if(sstatus == Socket::Done)
                    {
                        stringBuffer = buffer;
                        if(stringBuffer != "SEND_AGAIN")
                        {
                            i = 0;
                            while(i < MAX_CLIENTS)
                            {
                                if(client[i].status == "Looking for weapons" && stringBuffer == client[i].nickname && !playerFound)
                                {
                                    client[id].mateid = client[i].id;
                                    client[id].mate = client[i].nickname;
                                    client[i].mate = client[id].nickname;
                                    client[i].mateid = id;
                                    playerFound = true;
                                    //weapons[id].initialize(0, 0, id, "basic", 10);
                                    client[id].status = "In Game";
                                    ss.str(string());
                                    ss << client[id].mateid;
                                    stringBuffer = "IN_GAME|" + ss.str();
                                    //client[id].helm.initialize(0, 0, id, "basic", 100, 0);
                                    memset(buffer, '\0', MAX_BUFFER);
                                    strcpy(buffer, stringBuffer.c_str());
                                    sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                                    thread communicate(communicationThread, id);
                                    communicate.detach();
                                }
                                i++;
                            }
                            if (!playerFound)
                            {
                                stringBuffer = "INVALID_PLAYER";
                                memset(buffer, '\0', MAX_BUFFER);
                                strcpy(buffer, stringBuffer.c_str());
                                sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                            }
                        }
                        else
                        {
                            sleep(seconds(1));
                        }
    
                    }
                }
                if (sstatus != Socket::Done && sstatus != Socket::NotReady)
                {
                    cout <<"Error: client " <<id <<" disconnected." <<endl;
                    client[id].clean();
                    done = false;
                }
            }
        }
        cout <<"USCITO"<<endl;
        return done;
    }
    
    
    
    
    int manageClient(int id)
    {
        int result = 0;
        int i = 0;
        char buffer[MAX_BUFFER];
        string stringBuffer;
        string data[50];
        Socket::Status sstatus = Socket::Done;
        size_t receivedBytes;
        bool error = false;
        bool sent = false;
    
        buffer[0] = '0';
        client[id].ipAddress = client[id].socket.getRemoteAddress().toString();
        client[id].id = id;
    
        sstatus = client[id].socket.receive(buffer, MAX_BUFFER, receivedBytes);
        do
        {
            if (sstatus == Socket::Done)
            {
                stringBuffer = buffer;
                fragmentString(stringBuffer, "|", data);
                while(i < MAX_CLIENTS)
                {
                    if(!client[i].nickname.empty() and data[0] == client[i].nickname)
                    {
                        error = true;
                    }
                    i++;
                }
                i = 0;
                if(error == false)
                {
                    if(data[1] != "weapons" and data[1] != "helm")
                        error = true;
                    if(error == false)
                    {
    
                        client[id].nickname = data[0];
                        client[id].type = data[1];
                        cout <<"Client " <<id <<" has '" <<client[id].nickname <<"' as nickname and is a " <<client[id].type <<"." <<endl;
                        stringBuffer = "OK";
                        strcpy(buffer, stringBuffer.c_str());
                        sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                        client[id].status = "Waiting game";
                        sent = true;
                    }
                }
                if (error == true)
                {
                    stringBuffer = "ERROR:SEND AGAIN";
                    strcpy(buffer, stringBuffer.c_str());
                    sstatus = client[id].socket.send(buffer, MAX_BUFFER);
                    sstatus = client[id].socket.receive(buffer, MAX_BUFFER, receivedBytes);
                    error = false;
                }
            }
            if(sstatus == Socket::Partial)
            {
                cout <<"Error: partial data received on client " <<id <<"." <<endl;
                client[id].clean();
            }
            if(sstatus == Socket::Disconnected)
            {
                cout <<"Error: Client " <<id <<" disconnected" <<endl;
                client[id].clean();
            }
            if(sstatus == Socket::Error)
            {
                cout <<"Error: Internal Error for client " <<id <<"." <<endl;
                client[id].clean();
            }
        }
        while(sstatus != Socket::Partial and sstatus != Socket::Disconnected and sstatus != Socket::Error and sent != true);
        return result;
    }
    
    
    
    void communicationThread(int id) //shareyourmenu.ddns.net
    {
        Socket::Status status = Socket::Done;
        string inputString, outputString;
        string data[50];
        stringstream ss;
        list<ServerStatus>::iterator sStatus; //Server status
        list<Ship>::iterator shipsItr;
        char buffer[MAX_BUFFER];
        size_t received;
        client[id].socket.setBlocking(true);
        while(status == Socket::Done || status == Socket::NotReady)
        {
            /*RECEIVE PHASE******************************/
            memset(buffer, '\0', MAX_BUFFER);
            //sleep(milliseconds(5));
            status = client[id].socket.receive(buffer, MAX_BUFFER, received);
            if (status == Socket::Done || status == Socket::NotReady)
            {
                if (!client[id].input.readable && status == Socket::Done && buffer[0] != '\0')
                {
                    inputString = buffer;
                    fragmentString(inputString, "|", data);
                    if(!(data[0].empty()) && !(data[1].empty()) && !(data[2].empty()) && !(data[3].empty()) && !(data[4].empty()))
                    {
                        client[id].input.mousex = atoi(data[0].c_str());
                        client[id].input.mousey = atoi(data[1].c_str());
                        if(data[2] == "t")
                            client[id].input.leftMouseKey = true;
                        else
                            client[id].input.leftMouseKey = false;
                        if(data[3] == "t")
                            client[id].input.rightMouseKey = true;
                        else
                            client[id].input.rightMouseKey = false;
                        client[id].input.rotation = (float)atoi(data[4].c_str());
                        client[id].input.readable = true;
                    }
                //sleep(milliseconds(5));
                }
                //cout <<"Received    " <<inputString <<endl;
                //cout <<"Received from " <<id <<" " <<client[id].input.mousex <<" " <<client[id].input.mousey <<" " <<client[id].input.leftMouseKey <<" " <<client[id].input.rightMouseKey <<" " <<client[id].input.rotation <<endl;
    
                /*SEND PHASE******************************/
    
    
                if(!serverStatus.empty())
                {
                    outputString.clear();
                    sStatus = serverStatus.begin();
                    sStatus->nThreads->push_back(1);
                    shipsItr = sStatus->ships->begin();
                    while(shipsItr != sStatus->ships->end())
                    {
                        ss.str(string());
                        ss << "PL|";
                        ss << shipsItr->getPlayerId();
                        ss << "|";
                        ss << shipsItr->x;
                        ss << "|";
                        ss << shipsItr->y;
                        ss << "|";
                        ss << shipsItr->rotation;
                        ss << "|";
                        ss << shipsItr->weapon->rotation;
                        ss << "|";
                        if (shipsItr->moving)
                            ss << "t";
                        else
                            ss << "f";
                        ss << "|";
                        outputString = outputString + ss.str();
                        //cout <<"String: " <<outputString <<endl;
                        shipsItr++;
                    }
                    sStatus->nThreads->pop_back();
                }
    
                memset(buffer, '\0', MAX_BUFFER);
                //cout <<outputString.size()<<endl;7
                if (outputString.size() < MAX_BUFFER - 100)
                strcpy(buffer, outputString.c_str());
                else
                    cout <<"String too big"<<endl;
                status = client[id].socket.send(buffer, MAX_BUFFER);
            }
        }
        if (status != Socket::Done && status != Socket::NotReady)
        {
            cout <<"Client disconnected: " <<id <<"." <<endl;
            client[id].clean();
        }
    }
    
    

    Nel mentre che le leggi, e soprattutto quando leggi come invio i messaggi con il TCP, ricordati che è un prototipo. Mi basta che resti acceso per qualche minuto. (questo della memoria invece è un problema che non ho nessuna idea di come risolvere, e sarebbe un problema che mi porterei anche nella versione stabile).

    Da ciò che so nessuno di questi thread davvero fanno qualcosa alla memoria, e sono certo al 99,99%. L'unica cosa che sembra faccia sovraccaricare la memoria è creare l'oggetto.
Devi accedere o registrarti per scrivere nel forum
10 risposte