Date: Sat, 20 Sep 1997 20:48:26 +0000 (GMT) From: Terry Lambert <tlambert@primenet.com> To: michaelv@MindBender.serv.net (Michael L. VanLoon -- HeadCandy.com) Cc: tlambert@primenet.com, toor@dyson.iquest.net, nate@mt.sri.com, dyson@freebsd.org, karpen@ocean.campus.luth.se, current@freebsd.org Subject: Re: FYI: regarding our rfork(2) Message-ID: <199709202048.NAA23258@usr07.primenet.com> In-Reply-To: <199709200806.BAA21983@MindBender.serv.net> from "Michael L. VanLoon -- HeadCandy.com" at Sep 20, 97 01:06:02 am
next in thread | previous in thread | raw e-mail | index | archive | help
> >Exactly so. If you want this protection, implement using processes > >instead of threads. > >The problem is that I may pass auto variables between threads: > > I believe this is Just Plain Wrong. Auto variables inside a function > call are local to that specific instance of that function. Since a > specific instance of a function is instantiated on a specific thread > of execution, that means the local auto-variables have a scope local > to that specific thread, as well. To assume there is a need to > consider otherwise is to needlessly complicate a clean paradigm. > > >I might do this, for example, if my work items were DNS lookups, etc., > >which had to be accomplished serially, or with some maximum concurrency > >(say I start no more than 3 thread2's, and 10's of thread1's) because > >of load characteristics. > > This would be better accomplished with global metephores, critical > sections, and/or mutexes. Or, as stated above, metephores, critical > sections, and/or mutexes stored in a global namespace or class > specifically for this purpose. > > Of course, this assumes that such primitives are even available, which > they are not in standard libc. But neither are threads, indicating > additional libraries are already in use. By this logic, I should not call functions with non-global arguments when doing normal C programming. 8-). The question is whether or not you can conceive of a procedural interface where another thread does the required work. I can easily... and you should be able to as well. Say there's a task that requires you to do a DNS lookup and a lengthy initialization, where order is not important, followed by some network activity using the initialization and the DNS data. You could conceive that this might be implemented as something like: thread2() thread1() { { for(;;) { start_dns_lookup() ---> wait_for_request() /* start long initialization*/ make_dns_call() for(i = 0; i < MAXINIT; i++) /* wait DNS response*/ init_func( i); /* long init complete*/ wait_dns_lookup_complete() /* call returned*/ <--- signal_dns_complete() /* continue processing*/ } ... } } This lets the initialization and the DNS lookup proceed concurrently. The intent of threads is also to allow greater concurrency; it's not just to logically seperate groups of operations in an overall task by using different program counters. Because the act scheduled on the other thread is synchronized with the first thread, ther data pass from thread1 to thread2 by start_dns_lookup() is never in scope in thread2 and not in scope in thread one. This means it can e auto, or locally allocated, or whatever. There is no topological difference between allocating the storage off the stack, or allocating the storage off the stack, and then storing the pointer to the storage from the heap in a pointer variable -- it being allocated off the stack. 8-). This type of inter-thread cll is expected. If it were not, there would be no need for thread synchronization primitives. > If state needs to be kept among multiple threads, it should be done so > globally (or better, in a "global" namespace or class specificly for > this purpose). Since all stacks exist in the process address space, both stack and heap are global to all threads. The only relevent issue here is whether or not, when a buffer is passed out of scope, the sope that passed it remains intact until the out of scope operation is completed. With the failure case you are afraid of, I can cause the failure with a malloc'ed buffer as well: thread2() thread1() { { char *q = "hello world"; char *p = malloc( 20); for(;;) { send_buffer_to_thread_2() wait_for_buffer() /* XXX*/ ... ... ... strcpy( p, q); } } /* YYY*/ printf( "%s\n", p); free( p); } Say I get rid of from "XXX" to "YYY" in thread1. I now have a race between the deallocation and descoping of the buffer thread 2 is about to write to, and the act of thread 2 writing it. I also have a race between the printf() and the strcpy(). This means I can't safely assume the scheduler will act one way or the other (I can't anyway, but I might have done so in testing). Now say you want to make a "failure limitation" argument. The argument fails on the grounds that, after going out of scope in thread1, the area that will be written to by thread2 could have been reused by another thread. You can't make the argument that only thread1 is in danger of produsing erroneous results. So the failure case you are afraid of is not related to heap vs. stack allocation, it's related to inter-thread synchronization. Whether the buffer was on the heap or the stack is irrelevant. Regards, Terry Lambert terry@lambert.org --- Any opinions in this posting are my own and not those of my present or previous employers.
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199709202048.NAA23258>