Feature #161
closedNotizen zum Thema DCMTK und Multithreading
90%
Description
Generell: Unter Unix müssen Programme in MT-Anwendungen anstelle einiger
libc-Funktionen thread-safe-Varianten verwenden, die an dem Suffix _r
erkennbar sind. Unter Windows ist die gesamte libc thread safe, wenn man
als "Multithread" kompiliert. Alle statischen Puffer werden da für jeden
Thread separat instanziert. Der Sonderfall ist CygWin, was aussieht wie
ein Unix, aber die Windows-libc verwendet und daher keine _r-Funktionen
definiert.
Einige _r-Funktionen werden (zumindest unter Solaris) nur deklariert, wenn
mit -D_POSIX_PTHREAD_SEMANTICS übersetzt wird. Bei einigen ändern sich
dadurch die Parameter!
Es folgt eine Diskussion der MT-safe/unsafe-Funktionen in Posix und Unix98.
Page 32 of the 1996 Posix.1 standard says "All functions defined by Posix.1 and the C standard shall be thread-safe, except that the following functions need not be thread-safe: asctime() ctime() getc_unlocked()* getchar_unlocked()* getgrid() getgrnam() getlogin() getpwnam() getpwuid() gmtime() localtime() putc_unlocked()* putchar_unlocked()* rand() readdir() strtok() ttyname()" Note that a thread-safe XXX_r() version of the above are available, other than those with an asterisk. Also note that ctermid() and tmpnam() are only thread-safe if a nonnull pointer is used as an argument. However, POSIX and ANSI C specify only a small part of the "traditional UNIX programming environment", though it's a start. The real danger in reading the POSIX list is that most people don't really know what's included. While an inclusive list would be better than an exclusive list, that'd be awfully long and awkward. The Open Group (OSF and X/Open) has extended the Single UNIX Specification (also known as "SPEC1170" for it's 1,170 UNIX interfaces, or UNIX95) to include POSIX.1b-1993 realtime, POSIX.1c-1995 threads, and various extensions. It's called the Single UNIX Specification, Version 2; or UNIX98. Within this calendar year, it's safe to assume that most UNIX versions currently branded by The Open Group (as XPG3, UNIX93, UNIX95) will extend their brand validation to UNIX98. The interfaces specification part of the Single UNIX Specification, Version 2 (known as XSH5), in section 2.8.2, "Thread-safety", specifies that all interfaces defined by THIS specification will be thread-safe, except for "the following". There are two explicit lists, and one implicit. One is the POSIX list already quoted by Rich Stevens. The second is an additional list of X/Open interfaces: basename dbm_open fcvt getutxline pututxline catgets dbm_store gamma getw setgrent dbm_clearerr dirname gcvt l64a setkey dbm_close drand48 getdate lgamma setpwent dbm_delete ecvt getenv lrand48 setutxent dbm_error encrypt getgrent mrand48 strerror dbm_fetch endgrent getpwent nl_langinfo dbm_firstkey endpwent getutxent ptsname dbm_nextkey endutxent getutxid putenv The implicit list is a statement that all interfaces in the "Legacy" feature group need not be thread-safe. From another section, that list is: advance gamma putw sbrk wait3 brk getdtablesize re_comp sigstack chroot getpagesize re_exec step compile getpass regcmp ttyslot cuserid getw regex valloc <regexp.h> <varargs.h> <re_comp.h> loc1 __loc1 loc2 locs Obviously, this is still an exclusive list rather than inclusive. But then, if UNIX95 had 1,170 interfaces, and UNIX98 is bigger, an inclusive list would be rather awkward. (And don't expect ME to type it into the newsgroup!) On the other hand... beware that if you've got a system that doesn't claim conformance to POSIX 1003.1c-1995 (or POSIX 1003.1-1996, which includes it), then you're not guaranteed to be able to rely even on the POSIX list, much less the X/Open list. It's reasonable to assume that any implementation's libpthread (or equivalent, though that name has become pretty much defacto standard) is thread-safe. And it's probably reasonable to assume, unless specified otherwise, that "the most common" bits of libc are thread-safe. But without a formal statement of POSIX conformance, you're just dealing with "good will". And, even at that, POSIX conformance isn't validated -- so without validation by the UNIX98 branding test suite, you've got no real guarantee of anything.
Dies ist eine Liste aller Aufrufe von Funktionen, die evtl. nicht thread-
safe sind, im öffentlichen Teil des DCMTK, Stand 2002-04-11 (updated JR)
============================================================ ctime (allesamt unproblematisch da in Kommandozeilenprogrammen) ============================================================ ./imagectn/apps/imagectn.cc: printf("Cleaned up after child (%d) %s", child, ctime(&t)); ./imagectn/apps/imagectn.cc: ctime(&t)); ./dcmpstat/apps/dcmpsrcv.cc: << ") " << ctime(&t); ./dcmpstat/tests/msgserv.cc: COUT << ctime(&now); ============================================================ getenv ============================================================ ./dcmdata/libsrc/dcdict.cc:char* getenv() { ./dcmdata/libsrc/dcdict.cc: env = getenv(DCM_DICT_ENVIRONMENT_VARIABLE); ./dcmnet/libsrc/dulfsm.cc: if ((tcpNoDelayString = getenv("TCP_NODELAY")) != NULL) ./dcmnet/libsrc/dulfsm.cc: if ((TCPBufferLength = getenv("TCP_BUFFER_LENGTH")) != NULL) { ./dcmnet/libsrc/dcompat.cc: if (((env = getenv("TMPDIR")) != NULL) && access(env, AMODES) == 0) { ./dcmnet/libsrc/dul.cc: if ((tcpNoDelayString = getenv("TCP_NODELAY")) != NULL) { ./dcmnet/libsrc/dul.cc: if ((TCPBufferLength = getenv("TCP_BUFFER_LENGTH")) != NULL) { ./dcmjpeg/libijg12/jmemmgr.c:extern char * getenv JPP((const char * name)); ./dcmjpeg/libijg12/jmemmgr.c: if ((memenv = getenv("JPEGMEM")) != NULL) { ./dcmjpeg/libijg16/jmemmgr.c:extern char * getenv JPP((const char * name)); ./dcmjpeg/libijg16/jmemmgr.c: if ((memenv = getenv("JPEGMEM")) != NULL) { ./dcmjpeg/libijg8/jmemmgr.c:extern char * getenv JPP((const char * name)); ./dcmjpeg/libijg8/jmemmgr.c: if ((memenv = getenv("JPEGMEM")) != NULL) { ============================================================ rand (allesamt unproblematisch da in Kommandozeilenprogrammen) ============================================================ ./dcmpstat/apps/dcmmklut.cc: i1 = (unsigned long)(rand() * factor); ./dcmpstat/apps/dcmmklut.cc: i2 = (unsigned long)(rand() * factor); ./dcmpstat/apps/dcmmklut.cc: i1 = (unsigned long)(rand() * factor); ./dcmpstat/apps/dcmmklut.cc: i2 = (unsigned long)(rand() * factor); ============================================================ readdir (allesamt unproblematisch da in Kommandozeilenprogrammen) ============================================================ ./dcmdata/apps/dcmgpdir.cc: for (dp=readdir(dirp); dp!=NULL; dp=readdir(dirp)) { ./dcmpstat/apps/dcmprscu.cc: for (dp=readdir(dirp);((result == EC_Normal)&&(dp != NULL)); dp=readdir(dirp)) ============================================================ strerror ============================================================ ./dcmpstat/libsrc/dvpshlp.cc: logconsole->lockCerr() << "wait for child failed: " << strerror(errno) << endl; ./dcmpstat/apps/dcmpsrcv.cc: if (errno != ECHILD) CERR << "wait for child failed: " << strerror(errno) << endl; ./dcmpstat/apps/dcmpsrcv.cc: CERR << "Cannot create association sub-process: " << strerror(errno) << endl; ./dcmpstat/apps/dcmpsrcv.cc: << " reason: cannot create association sub-process: " << strerror(errno) << endl ./dcmpstat/apps/dcmprscp.cc: if (errno != ECHILD) CERR << "wait for child failed: " << strerror(errno) << endl; ./dcmdata/libsrc/cmdlnarg.cc: ofConsole.lockCerr() << "INTERNAL ERROR: cannot map stderr to stdout: " << strerror(errno) << endl; ./dcmdata/libsrc/cmdlnarg.cc: ofConsole.lockCerr() << "INTERNAL ERROR: cannot unbuffer stdout: " << strerror(errno) << endl; ./dcmdata/libsrc/cmdlnarg.cc: ofConsole.lockCerr() << "INTERNAL ERROR: cannot unbuffer stderr: " << strerror(errno) << endl; ./dcmdata/libsrc/dcuid.cc: ofConsole.lockCerr() << "sysinfo: " << strerror(errno) << endl; ./imagectn/libsrc/dbindex.cc: CERR << "DB_lseek: cannot get current position: " << strerror(errno) << endl; ./imagectn/libsrc/dbindex.cc: CERR << "DB_lseek: cannot get end of file position: " << strerror(errno) << endl; ./imagectn/libsrc/dbindex.cc: CERR << "DB_lseek: cannot reset current position: " << strerror(errno) << endl; ./imagectn/libsrc/dbindex.cc: CERR << msg << ": " << strerror(errno) << endl; ./imagectn/libsrc/dbstore.cc: << "IMAGECTN_DB_ERROR: " << strerror(errno) << endl; ./imagectn/libsrc/dbstore.cc: << strerror(errno) << endl; ./imagectn/libsrc/dbutils.cc: CERR << phandle->indexFilename << ": " << strerror(errno) << endl; ./imagectn/apps/scemove.cc: fname, strerror(errno)); ./imagectn/apps/sceget.cc: fname, strerror(errno)); ./imagectn/apps/imagectn.cc: errmsg("cannot access %s: %s", opt_configFileName, strerror(errno)); ./imagectn/apps/imagectn.cc: errmsg("wait for child failed: %s", strerror(errno)); ./imagectn/apps/imagectn.cc: errmsg("wait for child failed: %s", strerror(errno)); ./imagectn/apps/imagectn.cc: strerror(errno)); ./imagectn/apps/tiquery.cc: imgFile, strerror(errno)); ./imagectn/apps/ti.cc: strerror(errno)); ./ofstd/libsrc/ofthread.cc: const char *str = strerror(code); ./ofstd/libsrc/ofthread.cc: const char *str = strerror(code); ./ofstd/libsrc/ofthread.cc: const char *str = strerror(code); ./ofstd/libsrc/ofthread.cc: const char *str = strerror(code); ./ofstd/libsrc/ofthread.cc: const char *str = strerror(code); ./wlistctn/libsrc/wrklstdb.cc: << "return code: " << strerror(errno) << endl; ./wlistctn/tests/wltest.cc: strerror(errno)); ./wlistctn/tests/wltest.cc: errmsg("Cannot open file: %s: %s", queryFilename, strerror(errno)); ./wlistctn/apps/wlistctn.cc: errmsg("%s: %s", baseWorklistDBPath, strerror(errno)); ./wlistctn/apps/wlistctn.cc: errmsg("wait for child failed: %s", strerror(errno)); ./wlistctn/apps/wlistctn.cc: errmsg("wait for child failed: %s", strerror(errno)); ./wlistctn/apps/wlistctn.cc: errmsg("Cannot create association sub-process: %s", strerror(errno)); ./dcmnet/libsrc/dimse.cc: dataFileName, strerror(errno)); ./dcmnet/libsrc/dimse.cc: dataFileName, strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf2, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "requestAssociationTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "ReplyAssociationTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "sendAssociationRJTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "sendAbortTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "sendReleaseRQTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "sendReleaseRPTCP"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf1, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "writeDataPDU"); ./dcmnet/libsrc/dulfsm.cc: sprintf(buf2, "TCP I/O Error (%s) occurred in routine: %s", strerror(errno), "writeDataPDU"); ./dcmnet/libsrc/dcompat.cc: CERR << s << ": " << strerror(errno) << endl; ./dcmnet/libsrc/dcompat.cc: CERR << s << ": " << strerror(errno) << endl; ./dcmnet/libsrc/dcompat.cc ./dcmnet/libsrc/dcompat.cc:char *strerror(int errornum) ./dcmnet/libsrc/dul.cc: sprintf(buf3, "TCP Initialization Error: %s, accept failed on socket %d", strerror(errno), sock); ./dcmnet/libsrc/dul.cc: sprintf(buf4, "TCP Initialization Error: %s, setsockopt failed on socket %d", strerror(errno), sock); ./dcmnet/libsrc/dul.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf1, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf2, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf3, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf4, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/libsrc/dul.cc: sprintf(buf5, "TCP Initialization Error: %s", strerror(errno)); ./dcmnet/apps/movescu.cc: errmsg("Cannot open file: %s: %s", fname, strerror(errno)); ./dcmnet/apps/findscu.cc: errmsg("Cannot open file: %s: %s", fname, strerror(errno)); ./dcmnet/apps/storescu.cc: errmsg("Cannot open file: %s: %s", fname, strerror(errno)); ./dcmnet/include/dcompat.h:char *strerror(int e); ============================================================ wait3 ============================================================ ./dcmpstat/libsrc/dvpshlp.cc: child = wait3(&status, options, &rusage); ./dcmpstat/apps/dcmpsrcv.cc: child = wait3(&status, options, &rusage); ./dcmpstat/apps/dcmprscp.cc: child = wait3(&status, options, &rusage); ./imagectn/apps/imagectn.cc: child = wait3(&status, options, &rusage); ./wlistctn/apps/wlistctn.cc: child = wait3(&status, options, &rusage); ./dcmnet/include/dcompat.h:int wait3(int *statusp, int options, struct rusage *rusage);