fork in nested loops - fork

can someone explain How many child processes do this program create?
the answer is 127, but I couldn't understand how they got it.
int create(int num){
int i;
for(i=0;i<num;i++)
fork();
}
int main() {
int i;
fork();
for(i=0;i<4;i++)
create(i);
return 0;
}

This really sounds like it's a homework problem for a class on operating systems, but it's an interesting problem so I'll answer it for you. First off, let's look at the code as follows. Functionally, it's the same thing, but it'll make things a little easier to digest. Also, to start, let's ignore that initial fork() call. We'll count how many there are if it weren't there, and then if we add it back in, we'll have the same amount of processes, times two.
int main() {
int i, j;
// fork();
for (i = 0; i < 4; i++) {
for (j = 0; j < i; j++) {
fork();
}
}
}
Now this is partly a math problem, and partly a programming problem. First, we need to understand what happens when we call fork(). When you create a child process, the child inherits it's own copy of all of the parent's variables at the variables' current values at the time at which the fork() call was made. So that means that now, the parent and the child have copies of the exact same variables with the exact same values, but they can modify those variables independently, and they won't effect each other. So then in the following simple example,
int main() {
int i = 0, pid = fork();
if (pid == 0) {
i = 1;
}
if (pid > 0) {
i = 2;
}
}
In the parents world, i gets the value 2, and in the child's world i gets the value 1, and these are now separate variables we're talking about so the parent can have what it wants, the child can have what it wants, they don't conflict, and everybody's happy.
Now, to answer your problem, we have to keep this in mind. So let's see how many processes we have first without the initial fork() call. Well now, the parent itself will spawn 6 child process. For each of those processes, the variables (i,j) will have values (1,0), (2,0), (2,1), (3,0), (3,1), and (3,2), respectively.
So the last child spawned at (3,2) will exit the loop, and won't spawn any more children. The child spawned at (3,1) will then continue the for loops, increment j, spawn another process, and then both children will see (i,j) at (3,2), exit the for loops, and then die. Then we had another child spawned by the parent at (3,0). Well now this child will continue through the for loops, spawn a child at (3,1) and (3,2), and then die, and then this new child spawned at (3,1) will spawn another child and then they'll die. I think we can see this is starting to get pretty complex, so we can represent this situation with the following graph.
Each vertex of the graph represents a process and the vertex labeled p is the parent process. The ordered pair on each of the edges represents the values of (i,j) at the time at which the child process was spawned. Notice how we can group the processes. In that first group, we have 1 process, the next, we have 2, the next 4, then 8, and we should see now how things are going. The next group will have 16 processes, and the next group will have 32. Therefore, if we count all the processes we have, including the parent, we have 64 process. Make sense so far?
Well now let's put that initial fork() call back in. That will cause the exact same situation that we just described to happen twice, which would give us 128 process in total, including the parent, which means that we have spawned 127 children.
So yeah, half math problem, half programming problem. Let me know your questions.
You could rewrite the first loop to be for (i = 1; i <= n; i++). Then I'm pretty sure we could say that in general, your parent process will spawn children, where

Related

Given a circular linked list, find a suitable head such that the running sum of the values is never negative

I have a linked list with integers. For example:
-2 → 2
↑ ↓
1 ← 5
How do I find a suitable starting point such that the running sum always stays non-negative?
For example:
If I pick -2 as starting point, my sum at the first node will be -2. So that is an invalid selection.
If I pick 2 as the starting point, the running sums are 2, 7, 8, 6 which are all positive numbers. This is a valid selection.
The brute force algorithm is to pick every node as head and do the calculation and return the node which satisfies the condition, but that is O(𝑛²).
Can this be done with O(𝑛) time complexity or better?
Let's say you start doing a running sum at some head node, and you eventually reach a point where the sum goes negative.
Obviously, you know that the head node you started at is invalid as an answer to the question.
But also, you know that all of nodes contributing to that sum are invalid. You've already checked all the prefixes of that sublist, and you know that all the prefixes have nonnegative sums, so removing any prefix from the total sum can only make it smaller. Also, of course, the last node you added must be negative, you can't start their either.
This leads to a simple algorithm:
Start a cumulative sum at the head node.
If it becomes negative, discard all the nodes you've looked at and start at the next one
Stop when the sum includes the whole list (success), or when you've discarded all the nodes in the list (no answer exsits)
The idea is to use a window, i.e. two node references, where one runs ahead of the other, and the sum of the nodes within that window is kept in sync with any (forward) movement of either references. As long as the sum is non-negative, enlarge the window by adding the front node's value and moving the front reference ahead. When the sum turns negative, collapse the window, as all suffix sums in that window will now be negative. The window becomes empty, with back and front referencing the same node, and the running sum (necessarily) becomes zero, but then the forward reference will move ahead again, widening the window.
The algorithm ends when all nodes are in the window, i.e. when the front node reference meets the back node reference. We should also end the algorithm when the back reference hits or overtakes the list's head node, since that would mean we looked at all possibilities, but found no solution.
Here is an implementation of that algorithm in JavaScript. It first defines a class for Node and one for CircularList. The latter has a method getStartingNode which returns the node from where the sum can start and can accumulate without getting negative:
class Node {
constructor(value, next=null) {
this.value = value;
this.next = next;
}
}
class CircularList {
constructor(values) {
// Build a circular list for the given values
let node = new Node(values[0]);
this.head = node;
for (let i = values.length - 1; i > 0; i--) {
node = new Node(values[i], node);
}
this.head.next = node; // close the cycle
}
getStartingNode() {
let looped = false;
let back = this.head;
let front = this.head;
let sum = 0;
while (true) {
// As long as the sum is not negative (or window is empty),
// ...widen the window
if (front === back || sum >= 0) {
sum += front.value;
front = front.next;
if (front === back) break; // we added all values!
if (front === this.head) looped = true;
} else if (looped) {
// avoid endless looping when there is no solution
return null;
} else { // reset window
sum = 0;
back = front;
}
}
if (sum < 0) return null; // no solution
return back;
}
}
// Run the algorithm for the example given in question
let list = new CircularList([-2, 2, 5, 1]);
console.log("start at", list.getStartingNode()?.value);
As the algorithm is guaranteed to end when the back reference has visited all nodes, and the front reference will never overtake the back reference, this is has a linear time complexity. It cannot be less as all node values need to be read to know their sum.
I have assumed that the value 0 is allowed as a running sum, since the title says it should never be negative. If zero is not allowed, then just change the comparison operators used to compare the sum with 0. In that case the comparison back === front is explicitly needed in the first if statement, otherwise you may actually drop it, since that implies the sum is 0, and the second test in that if condition does the job.

Can shared memory be inconsistent between OpenMP parallel regions?

I'm writing a tool to test some graph algorithms. The tool has to go through all the edges in the graph and mark nodes at either end under certain conditions. It then has to go through all the nodes, ensuring they were all marked. The code should be pretty straight-forward, but I'm having some concurrency issues.
Here is my code:
#pragma omp parallel for reduction(+:seen_edges) default(shared)
for (size_t i = 0; i < n_edges; i++)
{
int64_t v0 = node_left(edges[i]), v1 = node_right(edges[i]);
// Do some work...
// This is where I mark nodes if the other end of the edge corresponds to the parent array
if (v0 != v1)
{
if(parents[v0] == v1)
reached[v0] = true;
if(parents[v1] == v0)
reached[v1] = true;
}
// Do more work...
}
#pragma omp parallel for default(shared)
for (size_t i = 0; i < n_nodes; i++)
{
if (i != source && !reached[i])
error("No traversed edge leading to node", i, &n_errors);
}
The reached array is initialised to false everywhere.
I can guarantee that on the input I'm using all the nodes should be marked, and thus no error should be printed. However, sometimes, some nodes remain unmarked.
I think memory shared should be consistent between OpenMP parallel regions, and I never set any element in reached to false except for at initialisation. The implicit barrier at the end of the first region should prevent any thread from going into the second one until all edges have been checked (and all nodes marked on this test input).
I see two possible options, but have no further explanation:
Some kind of data race is going on. But because I never set elements back to false, even if multiple threads try to write to a location at the same time, should that element eventually become true?
The elements are set to true, but memory is not consistent between threads in the second parallel region. Can this even happen in OpenMP?
If someone has any insight, I'd be grateful. Cheers.
Edit: The // Do work parts don't use the reached array, and parents is never modified in the program.

Having a hard time to understand for loops and nested for loops [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
So, I understand programming very well,... but recently I came across with for loops and especially the nested ones.. I simply can't understand. It simply won't enter in my head. Can anyone give me some tips on how to perceive these loops better (or... at all)?
Thanks in advance
A program describes a sequence of operations for the computer to perform. Among those operations may be some subsequences that the computer should repeat multiple times. Instead of literally repeating these subsequences the appropriate number of times in the program source code (which in many cases is impossible), you can use a construct that tells the computer that at the end of such a subsequence it should return or 'loop' back to the beginning of that subsequence. These types of constructs are conventionally called "loops".
In some cases, a repeating subsequence of operations itself contains a subsequence of operations that should be repeated multiple times as part of performing the one iteration of the containing sequence. That, too, can be represented via a loop construct.
Example: algorithm for cleaning the windows in my house
Get cleaning supplies from the closet
If there are no more dirty windows then stop. Else,
Go to the next dirty window.
Spray cleaner
Wipe window
If it's not clean enough then go back to step 3.1
Go back to step 2.
That has two loops: an outer one comprising every step except the first, and an inner one comprising steps 3.1 through 3.3.
Often, there is some kind of initialization or starting state that the must be reached before starting the loop. In the example, I must have my cleaning supplies at hand before I can actually clean any windows, and I want to start at the first window.
In most interesting cases, you don't know in advance how many times the program will need to run through a given loop. In the example, for instance, I might be able to predict the number of iterations of the outer loop as the number of windows in my house, but I cannot be certain how many iterations of the inner loop will be needed for any given window. Looping constructs handle this by providing flexible conditions for loop termination.
On the other hand, something has to change from iteration to iteration, else the repetition will never stop. In the simplest case, the thing that changes to trigger eventual break from the loop is (abstractly) the number of loop iterations that have been performed already. Often, though, we want a more flexible measure of whether any more iterations are needed, such as "is the window clean enough yet?"
A C/Java-style for loop formalizes those three elements: initialization (getting the supplies), termination condition (are there any more dirty windows?), and update (go to the next window). The initialization step is performed once, before the first iteration. The termination condition is tested before each iteration (and the loop terminates if it evaluates to false), and the update step is performed after each iteration, before testing the termination condition for the next iteration. When the loop terminates normally, the computer next executes the statement immediately after the loop body.
To continue the silly example:
for (
int window_number = 0;
window_number < TOTAL_NUMBER_OF_WINDOWS;
window_number = window_number + 1) {
Window currentWindow = windows[window_number];
do {
cleaner.spray(currentWindow);
cloth.wipe(currentWindow);
} while (currentWindow.isDirty());
}
In this case I represented the inner loop with a different loop construct (do { ... } while) because it fits more naturally with the facts that there is no initialization step required, I don't need to test for termination before the first iteration, and the update step is performed within the body of the loop. Since it wouldn't actually be harmful to test the termination condition before the first iteration, however, I can write the inner loop as a for loop, too. I just leave the parts I don't need blank (but I always need the two semicolon separators):
for (
int window_number = 0;
window_number < TOTAL_NUMBER_OF_WINDOWS;
window_number = window_number + 1) {
Window currentWindow = windows[window_number];
for (
/* no initialization */ ;
currentWindow.isDirty();
/* no (additional) update */) {
cleaner.spray(currentWindow);
cloth.wipe(currentWindow);
}
}
And that's most of what you need to know about loops in general and for loops in particular.
When we place one loop inside the body of another loop is called nested loop. And the outer loop will take control of the number of complete repetitions of the inner loop meaning the inner loop in the below example will run at least 10 times due to the condition a<10.
In the below example "Print B" will appear 200 times i.e. 20 * 10. The outer loop A will run inner loop B 10 times. And since inner loop B is configured to run 20 times the total number of times Print B will appear is 200.
// Loop: A
for(int a=0;a< 10;a++) {
// Loop: B
for(int b=1;b<20;b++) {
System.out.println("Print B");
}
}
There are many different types of for loops, but all behave similarly.
The basic idea of a for loop is that the code inside the for loop block will iterate for as long as the iterator is within a certain range.
i.e.
for(int i = 0; i < 10; i++)
{
int x = i;
}
In the code here (C++) the iterator is i, and the code block is int x = i. This means that the code block will be executed from i = 0, to i = 9, each time setting x to i and then increasing the value of i by 1.
Here you can see another description: C++ For Loops
And if you are working in Java: Java For Loops
Nested for loops work the same way, the only difference is that for each iteration of the outer loop you iterate completely the inner loop.
i.e.
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 5; i++)
{
int x = j;
}
}
Here you see that each time you execute the code inside the first for loop, you will execute the code inside the inner for loop to completion, or until j equals 5. Then you iterate the outer loop and run it again.
Hope this helps.

Check if a tree is a mirror image?

Given a binary tree which is huge and can not be placed in memory, how do you check if the tree is a mirror image.
I got this as an interview question
If a tree is a mirror image of another tree, the inorder traversal of one tree would be reverse of another.
So just do inorder traversal on the first tree and a reverse inorder traversal on another and check if all the elements are the same.
I can't take full credit for this reply of course; a handful of my colleagues helped with some assumptions and for poking holes in my original idea. Much thanks to them!
Assumptions
We can't have the entire tree in memory, so it's not ideal to use recursion. Let's assume, for simplicity's sake, that we can only hold a maximum of two nodes in memory.
We know n, the total number of levels in our tree.
We can perform seeks on the data with respect to the character or line position it's in.
The data that is on disk is ordered by depth. That is to say, the first entry on disk is the root, and the next two are its children, and the next four are its children's children, and so forth.
There are cases in which the data is perfectly mirrored, and cases in which it isn't. Blank data interlaced with non-blank data is considered "acceptable", unless otherwise specified.
We have freedom over using any data type we wish so long as the values can be compared for equivalence. Testing for object equivalence may not be ideal, so let's assume we're comparing primitives.
"Mirrored" means mirrored between the root's children. To use different terminologies, the grandparent's left child is mirrored with its right child, and the left child (parent)'s left child is mirrored with the grandparent's right child's right child. This is illustrated in the graph below; the matching symbols represent the mirroring we want to check for.
G
P* P*
C1& C2^ C3^ C4&
Approach
We know how many nodes on each level we should expect when we're reading from disk - some multiple of 2k. We can establish a double loop to iterate over the total depth of the tree, and the count of the nodes in each level. Inside of this, we can simply compare the outermost values for equivalence, and short-circuit if we find an unequal value.
We can determine the location of each outer location by using multiples of 2k. The leftmost child of any level will always be 2k, and the rightmost child of any level will always be 2k+1-1.
Small Proof: Outermost nodes on level 1 are 2 and 3; 21 = 2, 21+1-1 = 22-1 = 3. Outermost nodes on level 2 are 4 and 7; 22 = 4, 22+1-1 = 23-1 = 7. One could expand this all the way to the nth case.
Pseudocode
int k, i;
for(k = 1; k < n; k++) { // Skip root, trivially mirrored
for(i = 0; i < pow(2, k) / 2; i++) {
if(node_retrieve(i + pow(2, k)) != node_retrieve(pow(2, (k+1)-i)) {
return false;
}
}
}
return true;
Thoughts
This sort of question is a great interview question because, more than likely, they want to see how you would approach this problem. This approach may be horrible, it may be immaculate, but an employer would want you to take your time, draw things on a piece of paper or whiteboard, and ask them questions about how the data is stored, how it can be read, what limitations there are on seeks, etc etc.
It's not the coding aspect that interviewers are interested in, but the problem solving aspect.
Recursion is easy.
struct node {
struct node *left;
struct node *right;
int payload;
};
int is_not_mirror(struct node *one, struct node *two)
{
if (!one && !two) return 0;
if (!one) return 1;
if (!two) return 1;
if (compare(one->payload, two->payload)) return 1;
if (is_not_mirror(one->left, two->right)) return 1;
if (is_not_mirror(one->right, two->left)) return 1;
return 0;
}

Does Peterson's algorithm satisfy starvation?

I've been searching information on Peterson's algorithm but have come across references stating it does not satisfy starvation but only deadlock. Is this true? and if so can someone elaborate on why it does not?
Peterson's algorithm:
flag[0] = 0;
flag[1] = 0;
turn;
P0: flag[0] = 1;
turn = 1;
while (flag[1] == 1 && turn == 1)
{
// busy wait
}
// critical section
...
// end of critical section
flag[0] = 0;
P1: flag[1] = 1;
turn = 0;
while (flag[0] == 1 && turn == 0)
{
// busy wait
}
// critical section
...
// end of critical section
flag[1] = 0;
The algorithm uses two variables, flag and turn. A flag value of 1 indicates that the process wants to enter the critical section. The variable turn holds the ID of the process whose turn it is. Entrance to the critical section is granted for process P0 if P1 does not want to enter its critical section or if P1 has given priority to P0 by setting turn to 0.
As Ben Jackson suspects, the problem is with a generalized algorithm. The standard 2-process Peterson's algorithm satisfies the no-starvation property.
Apparently, Peterson's original paper actually had an algorithm for N processors. Here is a sketch that I just wrote up, in a C++-like language, that is supposedly this algorithm:
// Shared resources
int pos[N], step[N];
// Individual process code
void process(int i) {
int j;
for( j = 0; j < N-1; j++ ) {
pos[i] = j;
step[j] = i;
while( step[j] == i and some_pos_is_big(i, j) )
; // busy wait
}
// insert critical section here!
pos[i] = 0;
}
bool some_pos_is_big(int i, int j) {
int k;
for( k = 0; k < N-1; k++ )
if( k != i and pos[k] >= j )
return true;
}
return false;
}
Here's a deadlock scenario with N = 3:
Process 0 starts first, sets pos[0] = 0 and step[0] = 0 and then waits.
Process 2 starts next, sets pos[2] = 0 and step[0] = 2 and then waits.
Process 1 starts last, sets pos[1] = 0 and step[0] = 1 and then waits.
Process 2 is the first to notice the change in step[0] and so sets j = 1, pos[2] = 1, and step[1] = 2.
Processes 0 and 1 are blocked because pos[2] is big.
Process 2 is not blocked, so it sets j = 2. It this escapes the for loop and enters the critical section. After completion, it sets pos[2] = 0 but immediately starts competing for the critical section again, thus setting step[0] = 2 and waiting.
Process 1 is the first to notice the change in step[0] and proceeds as process 2 before.
...
Process 1 and 2 take turns out-competing process 0.
References. All details obtained from the paper "Some myths about famous mutual exclusion algorithms" by Alagarsamy. Apparently Block and Woo proposed a modified algorithm in "A more efficient generalization of Peterson's mutual exclusion algorithm" that does satisfy no-starvation, which Alagarsamy later improved in "A mutual exclusion algorithm with optimally bounded bypasses" (by obtaining the optimal starvation bound N-1).
A Rex is wrong with the deadlock situation.
(as a side note: the correct term would be starvation scenario, since for a deadlock there are at least two threads required to be 'stuck' see wikipedia: deadlock and starvation)
As process 2 and 1 go into level 0, step[0] is set to either 1 or 2 and thus making the advance condition of process 0 false since step[0] == 0 is false.
The peterson algorithm for 2 processes is a little simpler and does protect against starvation.
The peterson algorithm for n processes is much more complicated
To have a situation where a process starves the condition step[j] == i and some_pos_is_big(i, j) must be true forever. This implies that no other process enters the same level (which would make step[j] == i false) and that at least one process is always on the same level or on a higher level as i (to guarantee that some_pos_is_big(i, j) is kept true)
Moreover, only one process can be deadlocked in this level j. If two were deadlocked then for one of them step[j] == i would be false and therefor wouldn't be deadlocked.
So that means no process can't enter the same level and there must always be a a process in a level above.
As no other process could join the processes above (since they can't get into level j and therefor not above lelel j) at least one process must be deadlocked too above or the process in the critical section doesn't release the critical section.
If we assume that the process in the critical section terminates after a finite time, then only one of the above processes must be deadlocked.
But for that one to be deadlocked, another one above must be deadlocked etc.
However, there are only finite processes above, so eventually the top process can't be deadlocked, as it'll advance once the critical section is given free.
And therefor the peterson algorithm for n processes protects against starvation!
I suspect the comment about starvation is about some generalized, N-process Peterson's Algorithm. It is possible to construct an N-process version with bounded waiting, but without having one in particular to discuss we can't say why that particular generalization might be subject to starvation.
A quick Google turned up this paper which includes pseudocode. As you can see, the generalized version is much more complex (and expensive).

Resources