Thread: fork or thread in a server/client project

  1. #1
    Registered User
    Join Date
    Dec 2006
    Posts
    19

    fork or thread in a server/client project

    Hello.
    I keep asking myself whether to use fork or thread in a project I'm working on. I need to share some memory between the instances too.

    The situation is: I'm gonna send some messages from client to server, and on the server the messages are gonna be placed in a queue. When a message is received on the server a fork/thread is going to handle the message and put it into the queue. This queue has to be shared among the different forks/threads and only one fork/thread can write to the queue at the same time so no data is lost.

    I have thought about fork with shared memory and thread which uses the same memory but then with locks, but I really can't find out what to use? Can anyone help me out on which is the most simple and best solution?

  2. #2
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by daghenningsorbo View Post
    I have thought about fork with shared memory and thread which uses the same memory but then with locks, but I really can't find out what to use? Can anyone help me out on which is the most simple and best solution?
    Unless you are talking about something strange, forked processes in fact do not share memory -- they receive their own copy of the parent's memory. So that means anything done in a fork() DOES NOT happen in either the parent, or in a parallel fork, or even in the children of that fork.

    In other words, you have to use threads for this. If you haven't before, don't be intimidated by the documentation, it is actually pretty simple. It does sound like you will need to use some sort of lock on the queue -- either with a mechanism provided by the thread library or (fairly simply) using a global flag. In the latter case, all you need to worry about using, eg, pthreads, is something like:
    Code:
    void *myfunc();
    pthread_t mythread;
    pthread_create(&mythread,NULL,myfunc,NULL);
    pthread_detach(mythread);
    This will launch myfunc() as a parallel process with access to the same memory as any normal function (ie, globals). myfunc() must be of type void*, and it can accept one argument (passed via the final param to pthread_create, which is NULL above). It must end this way:
    Code:
    pthread_exit(NULL);
    If you then just use a global flag:
    Code:
    int QueLock = 0;
    It is easy enough to test the flag, set it to "true" (1) when modifying the queue, then set it back to false. If the initial test is true, you want to poll the flag on a slight delay* until it becomes false. As long your rountines only reset the flag if they have set them, and only access the queue when open (and always lock it first), everything should be fine.

    Of course, there are mechanisms in the pthread library which will allow you to do the same thing, differently . The pthread base documentation is here:
    http://cs.pub.ro/~apc/2003/resources...e/document.htm

    *make sure you use a delay or you will max out the processor!
    Last edited by MK27; 11-10-2009 at 02:22 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  3. #3
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> If you then just use a global flag:
    That's not proper synchronization. That's what pthread_mutex is for.

    gg

  4. #4
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    First up I forgot to add pthread_detach() to that last bit of code -- I edited it in now, but if daghenningsorbo comes back, note that.

    Quote Originally Posted by Codeplug View Post
    >> If you then just use a global flag:
    That's not proper synchronization. That's what pthread_mutex is for.
    No doubt that is what pthread_mutex is for, but if by "synchronization" and "proper" you mean "how I do it" or "my preferred method" fine, but there is nothing wrong, improper, undefined, dangerous, etc. about just using a global:
    Code:
    #include <stdio.h>
    #include <pthread.h>
    
    int Lock = 0, Total = 0;
    
    void *myfunc(void *count) {
    	int *tmp = count, id = *tmp;
    	printf("Lock: %d ID: %d\n",Lock,id); fflush(stdout);
    	if (!Lock) {
    		Lock = id;
    		if (Lock != id) {         /* double check lock to prevent simultaneous access */
    			(sleep(1));
    			myfunc(&id);
    		} else { 
    			sleep(1);      /* simulate our time consuming process */
    			Total++;
    			Lock = 0;
    		}
    	}
    	else {                  /* the basic poll loop */
    		(sleep(1));
    		myfunc(&id);
    	}
    	pthread_exit(NULL);
    }
    
    
    int main() {
    	int i;
    	pthread_t mythread[10];
    
    	for (i=0; i<10; i++) {
    		printf(" in main() i=%d ",i); fflush(stdout);
    		pthread_create(&mythread[i],NULL,myfunc,(void*)&i);
    		pthread_detach(mythread[i]);
    	}
    
    	while (Total<10) {
    		printf("->%d<-",Total);
    		sleep(1);
    	}
    	printf("Total: %d -- complete.\n",Total);
    
    	return 0;
    }
    Compile: gcc test.c -lpthread

    IMO what the OP is suggesting is simple and straightforward and this method will work flawlessly. It also has the advantage of being "familiar" and not leaning too heavily on the library -- just in case you do not have an extra afternoon to spend pouring over the rather tedious pthread docs.
    Last edited by MK27; 11-10-2009 at 04:34 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  5. #5
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> but there is nothing wrong, improper, undefined, dangerous, etc. about just using a global
    No, it's undefined - thus making it wrong.

    See section 4.10:
    http://www.opengroup.org/onlinepubs/...html#tag_04_10

    See A.4.10 for a "rationale":
    http://www.opengroup.org/onlinepubs/...l#tag_01_04_10

    gg

  6. #6
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by Codeplug View Post
    >> but there is nothing wrong, improper, undefined, dangerous, etc. about just using a global
    No, it's undefined - thus making it wrong.
    Did you actually look at the code I posted? There is nothing potentially undefined in there. From your own sources:

    Applications shall ensure that access to any memory location by more than one thread of control (threads or processes) is restricted such that no thread of control can read or modify a memory location while another thread of control may be modifying it.
    This is easy to understand and easy to implement. Notice, that according to the flow of execution, only ONE thread can access (for the purpose of modification) "Total" at a time. This is also true of the "Lock" variable -- only if the lock is open can a thread close (ie, modify) it, and only the thread that closed the lock can open it again.

    You could, of course, have a situation where two threads simultaneously find an open lock. To prevent this, the Lock is set to a unique thread ID and checked subsequent to the set. If the value is incorrect, we start again.

    I agree that just accessing globals wily-nily would lead to some "undefined" results. But the above code does satisfy the standards you site, it just does not depend so heavily on library functions to do so. They do not, after all, say "you must use a mutex", etc. The logic is simple and obvious.
    Last edited by MK27; 11-10-2009 at 04:32 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  7. #7
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    No, it's wrong.
    Code:
    if (!Lock) { /* read memory access */
        Lock = id; /* write memory access */
    ...
        Total++; /* read/write memory access */
    Those are all accesses to a memory location. Unsynchronized. Undefined. Easily broken due to the reasons listed in the rationale.

    gg

  8. #8
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by Codeplug View Post
    No, it's wrong.
    [code]
    if (!Lock) { /* read memory access */
    Lock = id; /* write memory access */
    Easily broken due to the reasons listed in the rationale.
    No, you are wrong.

    Quote Originally Posted by The Rationale
    Synchronization is still important even when accessing a single primitive variable (for example, an integer). On machines where the integer may not be aligned to the bus data width or be larger than the data width, a single memory load may require multiple memory cycles. This means that it may be possible for some parts of the integer to have an old value while other parts have a newer value. On some processor architectures this cannot happen, but portable programs cannot rely on this.
    There is the rationale. The only valid value to open the Lock is 0. The maximum value of the Lock is 10. Since on an x86 an integer is the same size or smaller than the the bus width, the point is theoretical. IN ANY CASE, there are no digits between 1-10 some half or even quarter or eighth of which could be mistaken for 0. So this point is mute.

    In fact, all those values are safe, because they do not extend beyond 8 bits. All of them have the same upper three bytes. And there is no architecture which does not load the lower byte in a single cycle. I could have made "Lock" an unsigned char just to be extra safe in my mind, but it would amount to the same thing in reality.

    Beyond that, it is worth pointing out that a value in memory actually cannot be changed "simultaneously". This is the reason for double checking the lock. Regardless of how many threads "simultaneously" believe the lock is open, the double check guarantees that only ONE of them will pass the final check (the one the changed the Lock value last) since NONE of them attempt to do it subsequently, and none of them can -- the lock is closed.

    There can be no simultaneous reads on Total, so that is completely safe. The one criticism I would make of the code is that the ID assignment is slightly sketchy, but it is not hard to come up with a better system.

    The idea is there and the method is sound.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  9. #9
    Registered User
    Join Date
    Nov 2008
    Posts
    75
    Quote Originally Posted by MK27 View Post
    No, you are wrong.



    There is the rationale. The only valid value to open the Lock is 0. The maximum value of the Lock is 10. Since on an x86 an integer is the same size or smaller than the the bus width, the point is theoretical. IN ANY CASE, there are no digits between 1-10 some half or even quarter or eighth of which could be mistaken for 0. So this point is mute.

    In fact, all those values are safe, because they do not extend beyond 8 bits. All of them have the same upper three bytes. And there is no architecture which does not load the lower byte in a single cycle. I could have made "Lock" an unsigned char just to be extra safe in my mind, but it would amount to the same thing in reality.

    Beyond that, it is worth pointing out that a value in memory actually cannot be changed "simultaneously". This is the reason for double checking the lock. Regardless of how many threads "simultaneously" believe the lock is open, the double check guarantees that only ONE of them will pass the final check (the one the changed the Lock value last) since NONE of them attempt to do it subsequently, and none of them can -- the lock is closed.

    There can be no simultaneous reads on Total, so that is completely safe. The one criticism I would make of the code is that the ID assignment is slightly sketchy, but it is not hard to come up with a better system.

    The idea is there and the method is sound.
    IMO on smp systems you still need at least an atomic_test_and_set function to do that.

  10. #10
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    You need learn about memory visibility/ordering among multiple processors.
    Quote Originally Posted by Rationale
    ... The memory operations of such machines are said to be weakly ordered. On these machines, the circular buffer technique shown in the example will fail because the consumer may see the new value of wrptr but the old value of the data in the buffer. In such machines, synchronization can only be achieved through the use of special instructions that enforce an order on memory operations. Most high-level language compilers only generate ordinary memory operations to take advantage of the increased performance. They usually cannot determine when memory operation order is important and generate the special ordering instructions. Instead, they rely on the programmer to use synchronization primitives correctly to ensure that modifications to a location in memory are ordered with respect to modifications and/or access to the same location in other threads. Access to read-only data need not be synchronized. The resulting program is said to be data race-free.
    >> This is the reason for double checking the lock.
    DCL doesn't work, for the same reasons listed in the Rationale...
    Perhaps Meyers and Alexandrescu can explain it better (but you have read the whole thing): http://www.aristeia.com/Papers/DDJ_J...04_revised.pdf

    Once you understand the above linked "C++ and the Perils of Double-Checked Locking" paper - then you'll understand the full Rationale, and why you are in-fact wrong.

    gg

  11. #11
    Registered User
    Join Date
    Dec 2006
    Posts
    19
    Well, this topic became a real discussion

    I didn't meant fork WITH shared memory. Just a bad sentence. I meant using fork and the using shared memory to gain access to memory from the different processes created with fork.

    But I think I'm gonna stick with your solution about threads. I'll try it out, and maybe post a new reply if I stumble into some problems.

    Cheers

  12. #12
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by daghenningsorbo View Post
    But I think I'm gonna stick with your solution about threads. I'll try it out, and maybe post a new reply if I stumble into some problems.

    Cheers
    Yeah, definitely with threads -- I am pretty sure your other idea is in fact impossible. The OS is not so flexible as to allow a process access to the memory of another process, and IMO based on similar ideas that have been discussed here in the past, shmem will be a big disappointment. So threading is the only choice, unless you want to use some kind of database (ie, keep the queue in a file).

    btw, my advice about not using mutexes is NOT good advice
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  13. #13
    Registered User slingerland3g's Avatar
    Join Date
    Jan 2008
    Location
    Seattle
    Posts
    603
    Interesting discussion and some good reading. MK27, but does myfunc() require a variable passed in to insure your unique ID for your LOCK as called from pthread_create()?

    Also, not sure, but calling your myfunc(), to insure your LOCK is in place, from within pthread_create() even safe? I have not done a lot of threading programs so, this may be academic and just some cleaver programming technique.

  14. #14
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by slingerland3g View Post
    Interesting discussion and some good reading. MK27, but does myfunc() require a variable passed in to insure your unique ID for your LOCK as called from pthread_create()?
    Yeah, that's what I meant by sketchy -- maybe. I kind of figure when a function is called, it's stack is created. The processor cannot defer this at all. Which pthread_create does call the function, so that is when id will be set -- before the pthread_detach(). Meaning the for loop could not advance first and create a situation where two threads get the same ID. This will be true whether there are 1 or 2 or 4 or 8 processors, because the instructions in the for loop must be executed in order.

    Also, not sure, but calling your myfunc(), to insure your LOCK is in place, from within pthread_create() even safe? I have not done a lot of threading programs so, this may be academic and just some cleaver programming technique.
    Not sure what you mean. myfunc() would be called with pthread_create even if I were using conventional methods (that is the conventional method AFAIK). But to be honest I don't do a lot of threading either. I think this method works fine, despite the objections, but no, I wouldn't submit it for homework or use it in a commercial application

    I actually haven't looked at the pdf Codeplug posted yet. I would be interested to know whether by "double-locking" this refers to the double-checking technique I employed (which was just an original idea, for what that is worth) or whether Codeplug is just associating two completely different things based on the fact that they both have the word "double" in them.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  15. #15
    Registered User slingerland3g's Avatar
    Join Date
    Jan 2008
    Location
    Seattle
    Posts
    603
    Quote Originally Posted by MK27 View Post
    Yeah, that's what I meant by sketchy -- maybe. I kind of figure when a function is called, it's stack is created. The processor cannot defer this at all. Which pthread_create does call the function, so that is when id will be set -- before the pthread_detach(). Meaning the for loop could not advance first and create a situation where two threads get the same ID. This will be true whether there are 1 or 2 or 4 or 8 processors, because the instructions in the for loop must be executed in order.



    Not sure what you mean. myfunc() would be called with pthread_create even if I were using conventional methods (that is the conventional method AFAIK). But to be honest I don't do a lot of threading either. I think this method works fine, despite the objections, but no, I wouldn't submit it for homework or use it in a commercial application

    I actually haven't looked at the pdf Codeplug posted yet. I would be interested to know whether by "double-locking" this refers to the double-checking technique I employed (which was just an original idea, for what that is worth) or whether Codeplug is just associating two completely different things based on the fact that they both have the word "double" in them.
    Thanks, I got it now. To help clarify my second point, I was just asking if calling your myfunc() as an argument to pthread_create was viable, and safe in regards to checking for a LOCK on an existing thread -- just no too familiar with pthread_create(() to know otherwise.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Terminating secondary thread from another thread
    By wssoh85 in forum C++ Programming
    Replies: 13
    Last Post: 12-19-2008, 05:14 AM
  2. Messaging Between Threads, Exiting Thread On Demand, Etc.
    By HyperShadow in forum C++ Programming
    Replies: 10
    Last Post: 06-09-2007, 01:04 PM
  3. fork() inside a thread
    By rpalmer in forum C Programming
    Replies: 3
    Last Post: 05-25-2007, 02:29 AM
  4. [code] Win32 Thread Object
    By Codeplug in forum Windows Programming
    Replies: 0
    Last Post: 06-03-2005, 03:55 PM
  5. How to add a file to a project in bloodshed.
    By Smeep in forum C++ Programming
    Replies: 4
    Last Post: 04-22-2005, 09:29 PM

Tags for this Thread