Skip site navigation (1)Skip section navigation (2)
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>