Black Friday = Pricewatch Bekijk onze selectie van de beste Black Friday-deals en voorkom een miskoop.
Toon posts:

[C++] Bind() call komt niet terug voor UDP socket

Pagina: 1
Acties:

Verwijderd

Topicstarter
ik zit hier met een vreem probleem waar zelfs google geen antwoord op kon vinden.

ik probeer een hele simpele C++ versie te schrijven van een UDP server en client zoals beschreven in 'Beej's Guide to Network Programming'. het mag eigenlijk de naam C++ versie niet dragen omdat het enige wat ik heb toegevoegd is een namespace, een helper functie die wat overloading gebruikt en een log functie die een timestamp toevoegt.

het geheel compileert ook keurig met g++ onder kubuntu 64bit. start ook keurig totdat het de bind systemcall maakt. deze keert gewoon niet terug. geen error, geen waarschuwing. zelfs geen return waarde. ik heb zelfs alle argumenten eerst zelfstandig verwerkt en gecheckt alvorens ze aan de bind() systemcall te geven. ik heb een printf voor en direct na de call geplaast en de eerste printf wordt weergegeven de 2e verschijnt nooit. het is alsof de systemcall nooit terug keert naar userspace.

het programma blocked gewoon netzoals je op een read of accept zou verwachten maar voorzover ik weet blocked een bind call nooit of in iedergeval niet uren lang. hij lukt of hij faalt. mijn programma hangt gewoon. ik kan het alleen beindigen met ctrl+c.

om zeker te zijn dat het niet aan mijn systeem lag heb ik het origineel waar ik mijn C++ versie van heb afgeleid, geschreven in C, ook gecompileerd en hier werkt alles zoals het moet.

wat zie ik over het hoofd? waar zit mijn fout? goede tutorials over UDP server sockets zijn er ook niet echt. en een goed C++ udp server socket tutorial moet ik nog vinden want vrijwel alle tutorials gaan ervan uit dat de C systemcalls gewoon in C++ kunnen worden gebruikt en ik zou ook niet weten waarom niet. nou, in mijn geval niet dus. wie helpt mij op weg?

Link naar Bjee's guide:
http://www.beej.us/guide/...html/multipage/index.html

en de sectie over UDP sockets met de werkende C sourcecode:
http://www.beej.us/guide/...lientserver.html#datagram

en hier de server-sectie van mijn C++ versie in wording:
C++:
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
#include <ctime>
#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#include "udpmirror.hpp"


bool udpmirror::Server()
{
 LogMsg("Running UDPMirror in server-mode.");
 int    my_fd;
 int    received;
 int    report_len  = 100;
 char   report[report_len];
 char   buf[BUFSIZE];

 sockaddr_in my_server_addr;
 sockaddr_in sender_addr;
 socklen_t   addr_len   = sizeof(sender_addr);

    LogMsg("Opening server socket.");
    my_fd  = socket(PF_INET, SOCK_DGRAM, 0);
    if(my_fd < 0){
      LogMsg("Failed to open server socket.");
     return false;
    }

    LogMsg("Setting-up sockaddr_in structure.");
     Setup_sockaddr_in("127.0.0.1", 8471, my_server_addr);

    LogMsg("Binding server socket.");
    if(bind(my_fd, (struct sockaddr *)&my_server_addr, sizeof my_server_addr) < 0){
      LogMsg("Failed to bind server to IP/port number.");
     return false;
    }

    LogMsg("Ready to receive messages.");
    for(unsigned int i = 0; i < 1; ++i){
     received   = recvfrom(my_fd, buf, (BUFSIZE - 1), 0, (struct sockaddr *)&sender_addr, &addr_len);
        if(received < 0){
         LogMsg("Failed to receive UDP packet from socket.");
        }else{
         memset(report, '\0', report_len);
         sprintf(report, "Received packet of %d bytes from %s.", received, inet_ntoa(sender_addr.sin_addr));
         LogMsg(report);
        }

    }

    LogMsg("closing socket.");
     close(my_fd);

 return true;
}

bool udpmirror::Setup_sockaddr_in(char ip[], unsigned short int port, sockaddr_in &sockaddr)
{
 return Setup_sockaddr_in(ntohl(inet_addr(ip)), port, sockaddr);
}

bool udpmirror::Setup_sockaddr_in(unsigned int ip, unsigned short int port, sockaddr_in &sockaddr)
{
 LogMsg("Filling in IP address and port number into sockaddr_in structure.");
    sockaddr.sin_family      = AF_INET;
    sockaddr.sin_port        = htons(port);
    sockaddr.sin_addr.s_addr = htonl(ip);

    memset(sockaddr.sin_zero, '\0', sizeof(sockaddr.sin_zero));
 return true;
}

void udpmirror::LogMsg(char msg[])
{
 clock_t since;
    since = (clock() / CLOCKS_PER_SEC);

    printf("\n[%05d] %s", since, msg);
}



welke ik compileerde met de command-line als:
g++ -o udpmirror_server udpmirror.cpp udpmirror_server.cpp

wie helpt mij op weg?

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Start op linux eens je programma met strace:
strace udpmirror_server

ASSUME makes an ASS out of U and ME


  • zeroxcool
  • Registratie: Januari 2001
  • Laatst online: 03-11 23:24
Verwijderd schreef op zaterdag 02 augustus 2008 @ 08:50:
(...)
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ik denk dat hier de fout zit:

    LogMsg("Ready to receive messages.");
    for(unsigned int i = 0; i < 1; ++i){
     received   = recvfrom(my_fd, buf, (BUFSIZE - 1), 0, (struct sockaddr *)&sender_addr, &addr_len);
        if(received < 0){
         LogMsg("Failed to receive UDP packet from socket.");
        }else{
         memset(report, '\0', report_len);
         sprintf(report, "Received packet of %d bytes from %s.", received, inet_ntoa(sender_addr.sin_addr));
         LogMsg(report);
        }

    }

    LogMsg("closing socket.");
     close(my_fd);

 return true;
}
(...)
Ik weet niet of recvfrom blocking is of niet, maar kijk eens goed naar je for conditie. Nu wordt die loop namelijk maar één keer uitgevoerd...

zeroxcool.net - curity.eu


Verwijderd

Topicstarter
@zeroxcool
zover komt mijn programma dus niet eens. de for loop heb ik inderdaad zo geschreven zodat deze maar 1 keer loopt. puur om te testen. het programma is een simpel echo programma op basis van udp. de server start een upd listen socket en meerdere clients kunnen een udp paket er heen sturen welke de server dan op antwoord en weer terug stuurt. maar zover ben ik dus nog niet omdat bind gewoon hangt.

@H!GHGuy
ik zal strace eens proberen en de output hier posten.

ik programmeer nog niet zo heel lang in C/C++ maar heb vele jaren ervaring in PHP programmeren en dan vooral command-line scripts. ik heb daarbij ook veel met de sockets extension gespeeld dus ik ken de basics. ik kan echter geen reden bedenken waarom een bind systemcall hangt.

Update:
ik heb mijn programma door strace gehaald en blijkbaar is het niet de bind systemcall maar de recvfrom die hangt. ook werd blijkbaar de printf die ik had geplaatst voordat de for-loop begon niet weergegeven. alsof het programma sneller blockte voordat deze naar stdout kon schrijven.

ik heb naar aanleiding hiervan de hele for-loop ge-comment en hergecompileerd. wat blijkt: het werkt. het was dus toch de recvfrom call die blockte en niet de bind call.

[ Voor 23% gewijzigd door Verwijderd op 02-08-2008 21:11 ]


  • zeroxcool
  • Registratie: Januari 2001
  • Laatst online: 03-11 23:24
Verwijderd schreef op zaterdag 02 augustus 2008 @ 20:39:
(...)
ik heb naar aanleiding hiervan de hele for-loop ge-comment en hergecompileerd. wat blijkt: het werkt. het was dus toch de recvfrom call die blockte en niet de bind call.
Misschien leuk voor mede GoT'ers je aangepaste code te posten ;).

zeroxcool.net - curity.eu


Verwijderd

Topicstarter
dan hierbij mijn volledige code, het is nog niet af maar voor andere die ook stoeien met udp listen sockets in C++, hier is het.
ik verklaar hierbij dat de broncode vermeld in deze post zich in het publieke domein bevindt. gebruik op eigen risico. de broncode is nog niet af en dat laat ik als een uitdaging en opdracht voor een ieder die deze broncode wil gebruiken voor zijn eigen doeleinden.
in udpmiror.hpp
C++:
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
/*
 This is the generic header file.
*/

#ifndef UDPMIRROR_HPP
#define UDPMIRROR_HPP

#include <netinet/in.h>


namespace udpmirror
{
    const unsigned short int UDPPORT = 8472;
    const unsigned       int UDPADDR = INADDR_ANY;
    const unsigned       int BUFSIZE = 100;

    bool Server();
    bool Client();
    bool Setup_sockaddr_in(char ip[],       unsigned short int port, sockaddr_in &sockaddr);
    bool Setup_sockaddr_in(unsigned int ip, unsigned short int port, sockaddr_in &sockaddr);
    void LogMsg(char msg[]);

}

#endif


in udpmirror_server.cpp
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
 This is the server or 'mirror' file.
*/

#include <stdio.h>


#include "udpmirror.hpp"


int main(int argc, char *argv[])
{
 printf("\r\n UDPMirror compiled on %s at %s.", __DATE__, __TIME__);
 udpmirror::LogMsg("Running Main from UDPMirror-Server.");

    udpmirror::Server();
 return 0;
}


in udpmirror_client.cpp
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
 This is the client file.
*/

#include "udpmirror.hpp"


int main(int argc, char *argv[])
{
 udpmirror::LogMsg("Running Main from UDPMirror-Client.");

    udpmirror::Client();
 return 0;
}


en als laaste udpmirror.cpp
C++:
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
/*
 This is the common source file.
*/


#include <ctime>
#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#include "udpmirror.hpp"


bool udpmirror::Server()
{
 LogMsg("Running UDPMirror in server-mode.");
 int    my_fd;
 int    received;
 int    report_len  = 100;
 char   report[report_len];
 char   buf[BUFSIZE];

 sockaddr_in my_server_addr;
 sockaddr_in sender_addr;
 socklen_t   addr_len   = sizeof(sender_addr);

    LogMsg("Opening server socket.");
    my_fd  = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(my_fd < 0){
      LogMsg("Failed to open server socket.");
     return false;
    }

    LogMsg("Setting-up sockaddr_in structure.");
     Setup_sockaddr_in("127.0.0.1", 8471, my_server_addr);

    LogMsg("Binding server socket.");
    if(bind(my_fd, (struct sockaddr *)&my_server_addr, sizeof my_server_addr) < 0){
      LogMsg("Failed to bind server to IP/port number.");
     return false;
    }

    LogMsg("Ready to receive messages.");

    for(unsigned int i = 0; i < 10; ++i){
     received   = recvfrom(my_fd, buf, (BUFSIZE - 1), 0, (struct sockaddr *)&sender_addr, &addr_len);
        if(received < 0){
         LogMsg("Failed to receive UDP packet from socket.");
        }else{
         memset(report, '\0', report_len);
         sprintf(report, "Received packet of %d bytes from %s:%d.", received, inet_ntoa(sender_addr.sin_addr), sender_addr.sin_port);
         LogMsg(report);
        }

    }

    LogMsg("closing socket.");
     close(my_fd);

 return true;
}

bool udpmirror::Client()
{
 LogMsg("Running UDPMirror in client-mode.");

}

bool udpmirror::Setup_sockaddr_in(char ip[], unsigned short int port, sockaddr_in &sockaddr)
{
 return Setup_sockaddr_in(ntohl(inet_addr(ip)), port, sockaddr);
}

bool udpmirror::Setup_sockaddr_in(unsigned int ip, unsigned short int port, sockaddr_in &sockaddr)
{
 LogMsg("Filling in IP address and port number into sockaddr_in structure.");
    sockaddr.sin_family      = PF_INET;
    sockaddr.sin_port        = htons(port);
    sockaddr.sin_addr.s_addr = htonl(ip);

    memset(sockaddr.sin_zero, '\0', sizeof(sockaddr.sin_zero));
 return true;
}

void udpmirror::LogMsg(char msg[])
{
 clock_t since;
    since = (clock() / CLOCKS_PER_SEC);

    printf("\n[%05d] %s", since, msg);
}


de server compile je middels:
g++ -o udpmirror_server udpmirror.cpp udpmirror_server.cpp

en de client (die nog niet is ge-implementeerd):
g++ -o udpmirror_client udpmirror.cpp udpmirror_client.cpp

doe er mee wat je wil en dat het een leerzame ervaring mag zijn voor wie dit vindt.

Verwijderd

De reden dat de laatste printf niet werd weergegeven is waarschijnlijk dat de buffer niet geflushed is. Eindig je printf met een newline, en je buffer wordt per direct geflushed.

Beter nog, gebruik de cout stream in plaats van printf. Let wel dat ook deze geflushed dient te worden.

  • bobo1on1
  • Registratie: Juli 2001
  • Laatst online: 19-10 00:17
Wat daar ook goed voor werkt is fflush(stdout);
Dat kan handig zijn als je geen newlines wilt gebruiken of als je snel moet pipen naar een ander programma

Impedance, a measure of opposition to time-varying electric current in an electric circuit.
Not to be confused with impotence.


Verwijderd

Topicstarter
dat klinkt inderdaad logisch. stdout is een inderdaad vaak gebuffered. fflush had ik zo nog niet aan gedacht.

ik vermijd de STL liever omdat deze niet thread safe is. hoewel dat voor dit programma niet uitmaakt en voor het schrijven naar stdout mischien ook niet uitmaakt omdat een write blocked als een andere tread er ook naar schrijft, maar toch. de standaard C library voldoet voor de meeste simpele doeleinden.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Ik zou je library documentatie eens checken. De STL is vaak wel threadsafe, maar niet atomair. Net zoals double.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-11 18:33
Verwijderd schreef op zondag 03 augustus 2008 @ 23:08:
ik vermijd de STL liever omdat deze niet thread safe is.
[sarcasm]
Om diezelfde reden heb ik mijn eigen container en string classes geschreven.
[/sarcasm]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Verwijderd schreef op zondag 03 augustus 2008 @ 23:08:
ik vermijd de STL liever omdat deze niet thread safe is.
(Naast wat MSalters zegt; Ik gebruik wel de term thread safe verder)

Noem eens een class library waar alles by default thread safe is?
Java? Not
.NET? Not
STL? Not (zie bvb http://www.sgi.com/tech/stl/thread_safety.html)

Als AL je containers, strings, ... volledig thread safe MOETEN zijn, dan is er iets mis in je design.

Er zijn zeker voorbeelden waar thread safety vereist is waar ook een container gebruikt wordt, maar dan duw je daar een lock of een active class of iets dergelijk rond (of maak je een thread-safe wrapper).

Thread safety is op dat niveau niet iets wat je by default wil. Wat met al die use cases waarin thread safety helemaal niet nodig is omdat slechts 1 enkele thread owner is van het object. Zonde van de performance impact van de locks of welk mechanisme ook gebruikt wordt.
Op dat niveau moet je nadenken over wat wel en niet thread-safe moet zijn.

ASSUME makes an ASS out of U and ME


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Verwijderd schreef op zondag 03 augustus 2008 @ 08:42:
Beter nog, gebruik de cout stream in plaats van printf. Let wel dat ook deze geflushed dient te worden.
std::endl zorgt voor een flush.

Het lijkt me wel handig om eerst duidelijk te maken wat er met thread safe wordt bedoeld. Je kan het hebben over twee verschillende containers onafhankelijk in twee verschillende threads gebruiken, dezelfde container in twee verschillende threads lezen of schrijven. De eerste gaat (tegenwoordig) meestal wel goed, de tweede vaak ook nog wel, de derde meestal niet. SGI en Dinkumware standard libraries zijn zeker thread safe in de eerste zin. Aangezien Visual Studio tegenwoordig werkt met Dinkumware license geeft dat op de meeste platforms dus geen problemen.

[ Voor 53% gewijzigd door Zoijar op 04-08-2008 10:13 ]


Verwijderd

Topicstarter
met thread safe bedoel ik bijvoorbeeld een stack waar een worker thread inkommende berichten in set. als 1 thread net een nieuwe bericht wil toevoegen terwijl de andere er net 1 wil afhandelen gaat dat fout zonder een mutex.

de STL is geen library maar een specificatie voor een library en in die specificatie is de STL niet thread safe. het hele concept van threading is afwezig en daarmee dus vendor afhankelijk.

nu kun je inderdaad heel simpel een mutex object toevoegen om een STL object te beschermen maar dan is je ontwerp juist fout omdat je dan niet alleen de last van het locken en unlocken bij de callee legt maar deze ook toegang geeft tot iets wat je juist wil verbergen. de callee moet met simpele pop and push methoden de stack kunnen gebruiken en het hele concurrency gedeelte aan de het stack object zelf overlaten.

nu kun je een wrapper class schrijven wat mischien wel de simpelste oplossing is maar dan ga je eigenlijk de STL lopen uitbereiden. inheriten van STL object is al niet aanbevolen en als je via association gaat werken wordt het er ook niet mooier op. en aangezien niemand elke deel van de STL ten volle gebruikt is het vaak een kleine moeite om een eigen container class te schrijven.

een ieder moet voor zichzelf afvragen of de STL geschikt is voor wat je probeert te doen. dit is ook het sterkste punt van C++. het laat de programmeur zelf kiezen of de STL geschikt is voor zijn doeleinden, of een andere library mischien beter is en of dat er mischien zelf wat in elkaar kan worden gezet. er is gewoon geen universele en geschikt voor alles library.

begrijp me niet verkeerd, de STL is een prachtige verzamelling templates welke in een single-threaded programma of programma's waar de threads niet een STL object delen prima gebruikt kan worden maar als je dat net wel nodig hebt moet je de STL niet gebruiken.

ik zou in mijn programma hierboven prima de STL kunnen gebruiken maar aangezien dit programma meer een test-case is om listening UDP sockets te verkennen en een voorloper is voor een andere programma wat vrijwel zeker multi-threaded gaat zijn en ik de STL dan toch niet zo kan gebruiken, heb ik ervoor gekozen dat in deze test-case ook niet te doen.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Volgens jouw redenatie is de standaard C library dan ook niet thread safe, of een built-in array :) Ik zie geen andere mogelijkheid dan een synchronisatie wrapper.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Het zou dom zijn om een stack class zelf de mutex lock/unlock te laten doen. Hoe kan die stack weten dat die alleen binnen 1 thread wordt gebruikt? Daarom doen goed ontworpen classes geen aannames over hun omgeving.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:04

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op woensdag 06 augustus 2008 @ 03:22:
met thread safe bedoel ik bijvoorbeeld een stack waar een worker thread inkommende berichten in set. als 1 thread net een nieuwe bericht wil toevoegen terwijl de andere er net 1 wil afhandelen gaat dat fout zonder een mutex.
Een stack is dan wel weer prima thread-safe zonder mutex (dus lockless) te implementeren, dus nee, dat gaat niet per definitie fout zonder mutex.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
strict genomen is elk stuk geheugen wat door 2 of meer threads wordt gelezen en geschreven unsafe en moet worden beschermd. meestal door een mutex.

somige functies in de C library zijn ook thread-unsafe. zolang een functie al zijn relevante informatie op de stack zet is er geen probleem. elke thread heeft zijn eigen stack en zitten dus niet in mekaars geheugen te rommelen. zo'n functie kan veilig tegelijk vanaf meerdere threads worden uitgevoerd. het zijn dan vooral de functies die static variablen gebruiken die moeilijke bugs kunnen veroorzaken. dus nee, de C standaard library is ook niet geheel thread-safe en dient net zoals de STL met goed doordachte reden te worden gebruikt.

het verschil tussen de C library en de STL is dat we bij de C library een enkele functie call spreken wat slechts een fractie van een seconde wat geheugen gebruikt en dan weer verdwijnt. een STL object heeft toch een wat langere levenspan en bestaat niet alleen uit geheugen maar ook de relevante methoden die het echt nuttig maken.

om een lang verhaal dus kort te maken, gebruik je een STL object in een enkel thread dan is dat dus prima bruikbaar, maar zodra de mogelijkheid onstaat dat een andere thread iets met datzelfde geheugen kan doen, dan is synchornizatie gewoon een vereiste.

@.oisyn
een lockless stack is inderdaad mogelijk maar zelden een beter keuze. alleen als performance je echt crucial is zoals in real-time systemen dan kan het een oplossing zijn. maar het hangt allemaal af van de scheduler en omstandigheden ofdat het ook echt sneller is. je verliest wel vrijwel altijd je portability omdat een lockless stack niet te implementeren is zonder een stuk in assembly te schrijven. een lockless stack in puur c/c++ is volgensmij niet mogelijk.

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-11 18:33
Verwijderd schreef op donderdag 07 augustus 2008 @ 06:38:
het verschil tussen de C library en de STL is dat we bij de C library een enkele functie call spreken wat slechts een fractie van een seconde wat geheugen gebruikt en dan weer verdwijnt. een STL object heeft toch een wat langere levenspan en bestaat niet alleen uit geheugen maar ook de relevante methoden die het echt nuttig maken.
Ik zie even niet waarom dat verschil iets zou uitmaken mbt thread safety?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Is the SGI STL thread safe?
Yes. However, you should be aware that not everyone uses the phrase "thread safe" the same way. See our discussion of thread safety for our design goals.

Thread-safety for SGI STL
SGI STL provides what we believe to be the most useful form of thread-safety. This explains some of the design decisions made in the SGI STL implementation.

Client must lock shared mutable containers
The SGI implementation of STL is thread-safe only in the sense that simultaneous accesses to distinct containers are safe, and simultaneous read accesses to to shared containers are safe. If multiple threads access a single container, and at least one thread may potentially write, then the user is responsible for ensuring mutual exclusion between the threads during the container accesses.

...
http://www.sgi.com/tech/stl/thread_safety.html
The Dinkum Compleat Libraries can be built either as multithreaded or single-threaded.

For the container objects defined in the Standard C++ Library, such as STL Containers and objects of template class basic_string, this implementation follows the widely adopted practices spelled out for SGI STL:

Multiple threads can safely read the same container object. (There are no unprotected mutable subobjects within a container object.)
Two threads can safely manipulate different container objects of the same type. (There are no unprotected shared static objects within a container type.)
You must protect against simultaneous access to a container object if at least one thread is modifying the object. (The obvious synchronization primitives, such as those in the Dinkum Threads Library, will not be subverted by the container object.)
http://www.dinkumware.com/manuals/?manual=compleat&page=thread_safety.html

[ Voor 35% gewijzigd door Zoijar op 07-08-2008 10:20 ]


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Ik zie ook het probleem niet in het gebruik van STL in non-shared variables en het gebruik van een locking wrapper rond de gelijksoortige container in de shared cases.
Van die wrapper implementeer je dan maar wat je nodig hebt. En telkens je iets nieuws wil gebruiken schrijf je't er gewoon bij.
Zolang performance niet kritisch is voldoet dit in de meeste gevallen.

Ik vraag me zelfs af hoeveel van die shared containers je wel denkt nodig te hebben?
We weten allemaal dat locking an sich niet het beste multi-threading model is (begrijp dit niet als dat het helemaal slecht is natuurlijk) dus hoeveel van die lock-protected containers blijven er over?
Bovendien moet je een container die gepassed wordt tussen 2 threads (dus ownership verandert zonder shared-access) helemaal niet thread-safe maken.
Zomaar STL overboord te gooien omdat je een multi-threaded applicatie maakt, vind ik nonsense.
Op mijn werk schrijven we ook in een heavily multi-threaded app (reken 50-100 threads) en daar wordt STL ook gewoon gebruikt.

In een tool die ik zelf aan het developen ben zie ik maar 1 echte shared container: die met ACLs.
En zelfs die kan ik er nog wel uithalen als ik dit zou willen. Verder gebeurt alles met message passing, locks komen er verder niet aan te pas.

ASSUME makes an ASS out of U and ME


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:04

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op donderdag 07 augustus 2008 @ 06:38:
@.oisyn
een lockless stack is inderdaad mogelijk maar zelden een beter keuze. alleen als performance je echt crucial is zoals in real-time systemen dan kan het een oplossing zijn. maar het hangt allemaal af van de scheduler en omstandigheden ofdat het ook echt sneller is. je verliest wel vrijwel altijd je portability omdat een lockless stack niet te implementeren is zonder een stuk in assembly te schrijven. een lockless stack in puur c/c++ is volgensmij niet mogelijk.
Of het beter is of niet heb ik geen uitspraak over gedaan. Jij beweerde simpelweg dat je een mutex nodig hebt - dat is helemaal niet zo. Echter, op het moment dat je overal locks nodig hebt moet je je nog maar eens flink achter je oren krabben of je design wel klopt.

Een degelijk OS heeft gewoon API functies voor dingen als locked compare-exchange e.d., en degelijke compilers hebben daar weer enigszins cross platform intrinsics voor. Bovendien kun je crossplatform lockless container libraries downloaden die gebruik maken van die API's/intrinsics, afhankelijk van je platform.

Dat lockless niet mogelijk is in puur C++ is een non-argument, omdat threads ook niet mogelijk zijn in puur C++, net zo min als mutexes, dus dan heb je de issue sowieso al niet. En met C++0x komt zowel een fatsoenlijke threading library als atomic types en operaties daarop (en mutexes ;)). En natuurlijk niet te vergeten een goed gedefinieerd memory model, wat het momenteel nog mist.

[ Voor 4% gewijzigd door .oisyn op 07-08-2008 11:04 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
@H!GHGuy
ik heb ook niet gezegd dat de STL slecht is. het heeft gewoon niet mijn voorkeur. de STL kan ook niet het antwoord op alles zijn natuurlijk. en het zal inderdaad vaak zijn dat STL containers niet gedeelt worden door meerder threads en dus perfect gebruikt kunnen worden in multi-threaded applicaties. maar een message stack is een goed voorbeeld waar de STL niet zomaar gebruikt kan worden. je zult deze moeten beschermen met iets zoals een mutex.

weet je zeker dat je message passing geen locks gebruikt? wat als 1 thread checked of er nog nieuwe messages zijn terwijl de andere er net eentje inzet?

@farlane
het gaat om de kans dat 2 threads hetzelfde geheugen gebruiken terwijl 1 thread deze
aanpast terwijl de andere dit probeert te lezen. in die zin is een functie welke static variablen gebruikt net zo thread unsafe als een STL container. in beide gevallen is het vragen om bugs of erger maar bij een STL container is de kans dat 2 threads tegelijk er iets mee doen toch groter door de grotere gemiddelde lifespan van dit geheugen.

het is eigenlijk alsof een collectie van functies dezelfde static variablen gebruiken. stel dat elke functie call 20ms duurt en je hebt 5 van zulke calls dan is er dus een 'window' van 100ms waarop het fout kan gaan. zou elke functie zijn eigen static variablen gebruiken dan is dat window maar 20ms. de kans dat de 2e thread een functie aanroept uit deze collectie is dus puur statistisch gezien 5x zo groot. dit kun je ook als een positief iets zien natuurlijk omdat het dan ook 5x zo groot is dat de bug zich vroegtijdig laat zien.

@.oisyn
maar ik programmer nu in C++ en niet in assembly. de vraag ging of dat het nut had een eigen stack class te schrijven of de STL te gebruiken waarop jij de optie opperde een lockless stack class the schrijven. de ironie is dat zowel een mutex als een lockless stack class intern op dezelfde atomaire instructies bouwen en dat het maken van een lockless stack class neerkomt op het maken van een stack class welke een lock object gebruikt welke intern assembly gebruikt om te locken. het enige verschill tussen een mutex object en zo'n lock object is dat een mutex blocked terwijl zo'n lock object dat niet doet.

je verplaast dus code van een threading library naar je eigen code en of dat nut heeft is net zo twijfel achtig als een eigen filesystem driver schrijven en dan rauwe block I/O doen op je harde schijf. 99% van de programma's laat dat soort dingen graag over aan de system libraries maar een paar programma's zoals bijvoorbeeld partition magic doen dit zelf om dat het voor deze programma's echt een doel dient. een lockless stack class heeft zeker zijn nut maar niet voor alle programma's. netzoals de STL of de C standaard library niet overal goed inzetbaar is.

dat C++0x een stap voorwaarts is mag duidelijk zijn want in dit tijdperk van multi-cores is threading een te algemeen concept geworden om afwezig te zijn in de specificaties.

eigenlijk zou van elke STL container 2 versie's moeten bestaan: een normale zoals we die nu al kennen en een die concurrency intern goed afhandelt met bijvoorbeeld een mutex. je zou dan een stack object maken als deze private blijft en een stack_n object ofzo waarneer je deze wilt delen door meerdere threads. de *_n versie is dan verder indentiek behalve dat elke mutatie eerst een mutex locked zodat het zeker is dat deze thread de enige is die naar het geheugen van het STL object schrijft. je zou dan ook een multiple readers/single writer model kunnen gebruiken zodat meerdere threads wel tegelijk kunnen lezen maar niet schrijven.

@Zoijar
als ik dat dus goed lees dan is dus ook de dunkim library niet threading-safe.

[ Voor 9% gewijzigd door Verwijderd op 08-08-2008 07:49 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Verwijderd schreef op vrijdag 08 augustus 2008 @ 07:34:
@H!GHGuy
en het zal inderdaad vaak zijn dat STL containers niet gedeelt worden door meerder threads en dus perfect gebruikt kunnen worden in multi-threaded applicaties.
Dat is de meest gangbare definite van thread safe :) Zoals michiel ook al eerder opmerkte, die andere locking/mutex logica moet buiten je library geplaatst worden. Je weet namelijk nooit wat voor usage pattern er is, dus is het zonde om daar aannames over te doen.
@farlane
het gaat om de kans dat 2 threads hetzelfde geheugen gebruiken terwijl 1 thread deze
aanpast terwijl de andere dit probeert te lezen. in die zin is een functie welke static variablen gebruikt net zo thread unsafe als een STL container.
Statics zijn idd een van de dingen die functies niet thread-safe (kunnen) maken. Je hebt het nu over concurrency control en thread safety. Uiteraard moet je iets van concurrency control invoegen als je een container in meerdere threads tegelijk gaat modificeren. Daar zal iedereen het mee eens zijn.
in beide gevallen is het vragen om bugs of erger maar bij een STL container is de kans dat 2 threads tegelijk er iets mee doen toch groter door de grotere gemiddelde lifespan van dit geheugen.
Dat is compleet onzin: of je hebt synchronized access nodig, of niet. Hoe lang je bezig bent aan een object maakt niets uit. Elke microseconde dat het fout kan gaat zal het ooit fout gaan.
het is eigenlijk alsof een collectie van functies dezelfde static variablen gebruiken. stel dat elke functie call 20ms duurt en je hebt 5 van zulke calls dan is er dus een 'window' van 100ms waarop het fout kan gaan. zou elke functie zijn eigen static variablen gebruiken dan is dat window maar 20ms. de kans dat de 2e thread een functie aanroept uit deze collectie is dus puur statistisch gezien 5x zo groot. dit kun je ook als een positief iets zien natuurlijk omdat het dan ook 5x zo groot is dat de bug zich vroegtijdig laat zien.
Ik kom liever niet bij een klant aan met het verhaal dat hun software statistisch gezien niet zo heel vaak zou moeten crashen ;)
eigenlijk zou van elke STL container 2 versie's moeten bestaan: een normale zoals we die nu al kennen en een die concurrency intern goed afhandelt met bijvoorbeeld een mutex. je zou dan een stack object maken als deze private blijft en een stack_n object ofzo waarneer je deze wilt delen door meerdere threads.
Slecht ontwerp, imho. Niet orthogonaal en nodeloos ingewikkeld. Plus dat de aannames die je in je library doet uiteindelijk nooit exact overeen zullen komen met wat je gebruiker wil. Het lijkt me veel beter om er gewoon een tweede layer met syncronisatie primitives bij te leveren. Als je een monitor wilt, dan wrap je er een monitor smart pointer om je contrainer heen. Als je een simpele scoped lock wilt, dan construct je die voor je aan de slag gaat, etc. En dat is overigens ook hoe het werkt nu.
@Zoijar
als ik dat dus goed lees dan is dus ook de dunkim library niet threading-safe.
Dan heb je verkeerd gelezen ;) In mijn wereld betekent "thread-safe" dat je functies reentrant zijn, of in ieder geval die illusie wekken -- soms door intern even een mutex te zetten, omdat er geen andere oplossing is. Dus ik moet twee STL containers naast elkaar, onafhankelijk, in verschillende threads kunnen gebruiken zonder dat er problemen van komen. Als je een object/container tegelijk wilt modificieren in twee threads, dan moet je zelf maar concurrency maatregelen invoegen. Dat is iets op applicatie niveau; thread safety is op library niveau. Let wel, vroeger waren de meeste STL implementaties inderdaad niet thread safe. En sommige C functies, zoals strtok(), zijn dat nog steeds niet.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:04

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op vrijdag 08 augustus 2008 @ 07:34:
maar ik programmer nu in C++ en niet in assembly.
Ik zeg toch juist net dat je helemaal geen assembly nodig hebt, maar alleen functies die je OS al aanbieden? Net als bij mutexes. Dus je kunt gewoon lekker in C++ blijven programmeren.

In Windows heb je zelfs al een implementatie van een lockless stack, middels InterlockedPushEntrySList() en InterlockedPopEntrySList()
de vraag ging of dat het nut had een eigen stack class te schrijven of de STL te gebruiken waarop jij de optie opperde een lockless stack class the schrijven.
Ik opperde helemaal niets. Jij zei dat je altijd een mutex nodig hebt als je 1 stack door meerdere threads concurrent wilt gebruiken. Ik zeg dat die mutex niet per se nodig is. Ik zeg niet wat beter is of wat niet, want dat hangt nou eenmaal van je usecases of je platform af (in onze codebase gebruiken we overigens liever lockless-containers, omdat de schedulers van Windows en de Xbox 360 niet heel fijn om lijken te gaan met meerdere threads die continu dezelfde critical section zitten te locken - het voordeel van lockless is hier dat het niet door de kernel heen hoeft en daardoor heel veel tijd scheelt).

[ Voor 5% gewijzigd door .oisyn op 08-08-2008 11:37 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
@Zoijar
dan heb ik een iets andere definitie van thread-safe. een functie is bij mij thread-safe wanneer deze uit 2 of meer verschillende threads tegelijk kan worden aangeroepen waarbij ik dezelfde methode maar op 2 losstaande object als verschillend zie. als een functie static variablen gebruikt kan deze niet tegelijk worden aangeroepn maar ook als een methode het geheugen van een object aanpast zonder concurrent aanpassingen uit te sluiten is dat niet thread-safe.

dat een methode reentrant is is dus eigenlijk een soort half thread safety en volgens de quoutes die jij poste is ook de dunkim library half thread safe. de programma code is thread safe maar het geheugen dat deze programma code mee werkt is dat niet.

en de lifespan van een STL container maakt puur theoretisch wel degelijk uit hoewel het allebij vragen om problemen is. je komt dus bij je klant aan die kan kiezen uit slecht en slechter. in beide gevallen moet je de derde optie kiezen. maar dat is theorie

hoezo zou het slecht zijn om verschillende versie van elke low-level container te hebben. op dit moment is de STL gewoon niet bruikbaar over meerdere threads en komt concurrency control geheel op het bord van de applicatie. vergeet niet dat ook een library een applicatie van een andere library kan zijn. hoezo is het slecht om deze wrapper classen die gewoon hoog nodig zijn om de STL over meerdere threads te kunnen gebruiken geen onderdeel van de STL zelf te maken en hoeveel tuning opties wil je hebben.

was het doel van de STL niet om veel gebruikte oplossingen samen te bundelen. middels wat polymorfisme kan je heel simpel zonder enige overhead dit doen. het probleem is dat de huidige STL niet uitbereidbaar is. de destructors zijn niet voor niets non-virtual. zoiets moet dus vanuit de STL specificaties zelf komen of je moet de halfe STL zelf opnieuw implementeeren.

maar wat de oplossing ook is: de huidige STL heeft het niet en telkens vinden we het wiel weer opnieuwe uit en worden er nieuwe classen bedacht welke niets anders doen dan een mutex of vergelijkbaar synchronisatie middel toevoegen om een al bestaand STL object. hoe makkelijk zou het zijn om gewoon een stack_n<message> te kunnen maken en deze zonder verdere last door meerder threads laten gebruiken. en wil je het readers-writers model wat tunen dan zou je 2 opties aan de constructor kunnen toevogen.

1 het maximaal aantal semaphores en 2 het maximaal aantal semaphores dat een enkele thread per keer kan locken. dus stel je hebt 32 semaphores. de readers locken gewoon 1 semaphore per keer terwijl de writers er maximaal 8 per keer locken. zodoende kun je balans tussen readers-starvation aan de ene kant en writers-starvation aan de andere kant tunen.

ik geloof dat het commitee ook al hard bezig is om al dit soort dingen aan de toekomstige versies van de STL toe te voegen. we zullen zien hoe het uitpakt. ik spreek alleen over nu.

@.iosyn
ik heb ook niet gezegd dat lockless stacks slecht zijn. je moet ze alleen met reden gebruiken en de brakke schedulers van windows en de xbox360 zijn daar blijkbaar reden voor. ik geloof dat pthreads ook een modus ondersteunt waarbij een deel van de threading overhead in userspace wordt gedaan wat toch een aantal context switches scheelt.

dat de win32 library een lockless stack aanbiedt wil nog niet zeggen dat andere OS'en dat ook zo makkelijk doen. mijn punt ging over portability. de pthread library is op vrijwel alle gangbare OS'en beschikbaar en ook vrijwel alle hardware architectuuren die aan threading kunnen doen. nu zal er vast wel ergens een lockless stack library zijn die ook op evenveel systemen werkt maar je zult als je een lockless stack nodig hebt en je moet portable zijn dan toch aardig zoeken en mogelijk zelf wat schrijven mogelijk in assembly.

de POSIX threads library daar in tegen is breed toepassbaar en implementeerbaar hoewel ook daar zeker limitaties zijn. ik sluit het ook niet uit dat er situaties zijn waarbij je een mutex zelf moet schrijven middels inline assembly. als de STL in zijn toekomstige versies een lockless stack zou implementeeren zou ik daar best tevreden mee kunnen zijn.

als ik de indruk heb gewekt dat stacks alleen met mutex'en over meerdere threads mogelijk zijn dan is dat niet zo bedoeld. ik bedoel dat in ruim de meeste gevallen lockless niet zinvol is en een mutex de betere oplossing is. het hangt allemaal af van je scheduler en omgeving.
Pagina: 1