User Tools

Site Tools


archive:pgt200lab01

Lab Work 2 - Processes and Threads

In this module we will learn about processes and threads - basic concept of code execution by an OS in a computer system.

Process: Introduction

A process is essentially a computer program being executed (a.k.a. 'running') on a computer hardware. In this exercise, we will look at how a process can be created and controlled. The codes in this section are specific to Linux platform and cannot be used on a Windows platform. There are equivalent functions in the Win32 API that can be used to achieve the same goal but they will NOT be discussed here.

Notes

Executing a process is also known as 'running' a program code. A shell (terminal or console or command prompt on Win32) is also a process that is started by another process in the Operating System (OS). When we type a program name at the shell prompt (i.e. execute a process), the shell actually creates a child process, and put the program in the newly created process space and initiate its code execution. The first step we need to understand is how a process can create another process.

Process Creation

On Linux, the function that enables us to create a process is called fork. The prototype is

pid_t fork(void);

which is declared in unistd.h. It basically duplicates the current process and the return value will indicate which process the code belongs to.

Simple Fork

A simple example:

smith.c
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
	pid_t smith = fork();
	printf("I am Agent Smith %d (%d)\n",getpid(),smith);
	return 0;
}

Using the program from movie 'The Matrix', the above code will duplicate itself, with both copies introducing themselves and exiting gracefully (of course, this is not the case in the movie :-D). You should note that pid_t is a data type defined to represent process ID (an integer value assigned by the operating system to all running processes). The introduction text includes 2 integers - the first is the process ID for the running process and the second is the value returned by fork.

If the example given above is executed, the output should be something like:

user@localhost:~$ smith
I am Agent Smith 5539 (5540)
I am Agent Smith 5540 (0)

The first line is 'printed' by the original process while the second line is 'printed' by the created process. This is indicated by the return value (assigned to local variable smith) - a value of 0 means that the process is the newly created process, while the original process will get the process ID for the newly created process. The fact that the two processes prints different value for the local variable smith also shows that the processes have separate address space.

To clarify this, let us modify the code further to check the address for the local variable smith:

smith.c
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
	pid_t smith = fork();
	printf("I am Agent Smith %d @%p (%d)\n",getpid(),&smith,smith);
	return 0;
}

Now, the output is something like:

user@localhost:~$ smith
I am Agent Smith 2698 @0x7fff7e6f81dc (2699)
I am Agent Smith 2699 @0x7fff7e6f81dc (0)

Notice that even the local variable have the exact same address, the value is still different. This is because that address is actually a virtual address (will be covered in subsequent topic).

Parent Child Fork

All processes created this way are usually called the child processes of the original process. Naturally, the original process is known as the parent process of the newly created process. To make the code reflect this scenario, let us rewrite into:

family.c
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
	pid_t child = fork();
	if(child == 0)
	{
		printf("I am a child %d with parent %d\n",getpid(),getppid());
	}
	else
	{
		printf("I am a parent %d with child %d\n",getpid(),child);
	}
	return 0;
}

Now, the output should be something like:

user@localhost:~$ family
I am a parent 6666 with child 6667
I am a child 6667 with parent 6666

Multiple Forks

Let us try to breed more child processes based on user request in command line parameter:

breed.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
	pid_t child;
	int loop, count, check, status = 0;
	/* must have exactly ONE parameter */
	if(argc!=2)
	{
		printf("Usage: '%s' <count>\n",argv[0]);
		return -1;
	}
	/* get count from command line */
	count = atoi(argv[1]);
	if(!count)
	{
		printf("No child required? Abort!\n");
		return -2;
	}
	/* loop to create child */
	for(loop=0;loop<count;loop++)
	{
		child = fork();
		if(child == 0)
		{
			child = getpid();
			printf("I am a child %d\n",child);
			status = child;
			break;
		}
		else
		{
			waitpid(child,&check,0);
			if(WIFEXITED(check))
				printf("Child ended with %d\n",WEXITSTATUS(check));
				/** note: WEXITSTATUS grabs just the LSByte */
		}
	}
	/* parent text here! */
	if(!status)
	{
		printf("I am a parent - just created %d sub-process(es)\n",count);
	}
	return status;
}

Please spend some time and try to understand the code. This can easily be the basis for your lab exercises in this topic. Now that we know how to create processes, let us try to find a way to control the created processes in order to make it more useful.

Process Control

This is actually part of Inter-Process Communication (IPC) sub-topic but I prefer to cover this as process control mechanism - showing how the processes can send small amount of information (control signal) between one another. On Linux, there are a few methods that can be used to achieve this: signals, pipes, files, shared memory and sockets. We are going to use only two methods in this course - signals and files.

Using Signals

Using signals takes advantage of a system's interrupt facility. The easiest way to implement signals is to use the signal (duh!) function declared in signal.h. Example:

ctrlsig.c
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
 
int flag_SIGUSR1 = 0;
int flag_SIGUSR2 = 0;
 
void signal_handler(int signum)
{
	switch(signum)
	{
	case SIGUSR1:
		flag_SIGUSR1 = 1;
		flag_SIGUSR2 = 0;
		break;
	case SIGUSR2:
		flag_SIGUSR1 = 0;
		flag_SIGUSR2 = 1;
		break;
	/** other signals are ignored? */
	}
}
 
int main(int argc, char *argv[])
{
	pid_t child;
	if(signal(SIGUSR1,signal_handler)==SIG_ERR)
	{
		printf("Cannot set handler for SIGUSR1\n");
		exit(1);
	}
	if(signal(SIGUSR2,signal_handler)==SIG_ERR)
	{
		printf("Cannot set handler for SIGUSR2\n");
		exit(1);
	}
	if((child=fork())==0)
	{
		/** child stuff - wait for parent */
		printf("[Child-%d] Waiting for parent... ",getpid());
		while(!flag_SIGUSR1); /* wait for parent signal */
		printf("YES! (flag1=%d)(flag2=%d)\n",flag_SIGUSR1,flag_SIGUSR2);
		/** child stuff - send ack to parent */
		printf("[Child-%d] Sending acknowledgement to parent.\n",getpid());
		kill(getppid(),SIGUSR1);
		/** child stuff - wait for parent */
		printf("[Child-%d] Waiting for parent... ",getpid());
		while(!flag_SIGUSR2); /* wait for parent signal */
		printf("YES! (flag1=%d)(flag2=%d)\n",flag_SIGUSR1,flag_SIGUSR2);
		/** child stuff - send ack to parent */
		printf("[Child-%d] Sending acknowledgement to parent.\n",getpid());
		kill(getppid(),SIGUSR2);
	}
	else
	{
		/** parent stuff - send 'command' to child */
		printf("[Parent-%d] Sending 'command' to child.\n",getpid());
		kill(child,SIGUSR1);
		/** parent stuff - wait for child */
		printf("[Parent-%d] Waiting for child response... ",getpid());
		while(!flag_SIGUSR1); /* wait for child signal */
		printf("YES! (flag1=%d)(flag2=%d)\n",flag_SIGUSR1,flag_SIGUSR2);
		/** parent stuff - send 'command' to child */
		printf("[Parent-%d] Sending 'command' to child.\n",getpid());
		kill(child,SIGUSR2);
		/** parent stuff - wait for child */
		printf("[Parent-%d] Waiting for child response... ",getpid());
		while(!flag_SIGUSR2); /* wait for child signal */
		printf("YES! (flag1=%d)(flag2=%d)\n",flag_SIGUSR1,flag_SIGUSR2);
	}
	printf("[%d] Done\n",getpid());
	return 0;
}

However, the implementation of signal function (ANSI C specification) varies even across versions of Linux (and UNIX) and therefore not recommended for practical use. The recommended function for this purpose is sigaction, but this is left for you to explore.

Using Files

The above example can be rewritten to use file-based control:

ctrlfile.c
#include <unistd.h>
#include <stdio.h>
 
#define PARENT_FLAG1 "PFLAG1"
#define PARENT_FLAG2 "PFLAG2"
#define CHILD_FLAG1 "CFLAG1"
#define CHILD_FLAG2 "CFLAG2"
 
void wait_flag(char *pname)
{
	FILE *pfile = 0x0;
	while(!pfile) pfile = fopen(pname,"r");
	fclose(pfile);
}
 
void send_flag(char *pname)
{
	FILE *pfile = fopen(pname,"w");
	if(pfile) fclose(pfile);
}
 
void hide_flag(char *pname)
{
	remove(pname); /* actually deletes the flag file! */
}
 
int main(int argc, char *argv[])
{
	pid_t child;
	if((child=fork())==0)
	{
		/** child stuff - wait for parent */
		printf("[Child-%d] Waiting for parent... ",getpid());
		wait_flag(PARENT_FLAG1); /* wait for parent signal */
		printf("YES!\n");
		/** child stuff - send ack to parent */
		printf("[Child-%d] Sending acknowledgement to parent.\n",getpid());
		send_flag(CHILD_FLAG1);
		/** child stuff - wait for parent */
		printf("[Child-%d] Waiting for parent... ",getpid());
		wait_flag(PARENT_FLAG2); /* wait for parent signal */
		printf("YES!\n");
		/** child stuff - send ack to parent */
		printf("[Child-%d] Sending acknowledgement to parent.\n",getpid());
		send_flag(CHILD_FLAG2);
	}
	else
	{
		/** parent stuff - send 'command' to child */
		printf("[Parent-%d] Sending 'command' to child.\n",getpid());
		send_flag(PARENT_FLAG1);
		/** parent stuff - wait for child */
		printf("[Parent-%d] Waiting for child response... ",getpid());
		wait_flag(CHILD_FLAG1); /* wait for child signal */
		printf("YES!\n");
		/** parent stuff - cleanup! */
		hide_flag(PARENT_FLAG1);
		hide_flag(CHILD_FLAG1); /** should be done by child? */
		/** parent stuff - send 'command' to child */
		printf("[Parent-%d] Sending 'command' to child.\n",getpid());
		send_flag(PARENT_FLAG2);
		/** parent stuff - wait for child */
		printf("[Parent-%d] Waiting for child response... ",getpid());
		wait_flag(CHILD_FLAG2); /* wait for child signal */
		printf("YES!\n");
		/** parent stuff - cleanup! */
		hide_flag(PARENT_FLAG2);
		hide_flag(CHILD_FLAG2); /** should be done by child? */
	}
	printf("[%d] Done\n",getpid());
	return 0;
}

Messaging with Files

The advantage of having file-based communication is we can send more than just 'signals' - we can now send sizable data. Given a set of source files to form a project for file-based communication - starting with the main source:

fmsg.c
/*----------------------------------------------------------------------------*/
#include <unistd.h>
#include <stdio.h>
/*----------------------------------------------------------------------------*/
#include "fmsglib.h"
/*----------------------------------------------------------------------------*/
#define DATAFILE "message.txt"
#define WFLAG "WRITEDONE"
#define RFLAG "READDONE"
/*----------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
	if(fork() == 0)
	{
		/** child stuff */
		char child_msg[MSG_SIZE_MAX];
		while(check_flag(WFLAG));
		read_message(DATAFILE,child_msg,MSG_SIZE_MAX);
		child_msg[MSG_SIZE_MAX-1]=0x0;
		printf("[Child-%d] MsgRead: %s\n",getpid(),child_msg);
		setup_flag(RFLAG);
		while(!check_flag(WFLAG));
		clear_flag(RFLAG);
	}
	else
	{
		/** parent stuff */
		char parent_msg[] = "I AM LEGEND!";
		write_message(DATAFILE,parent_msg);
		printf("[Parent-%d] MsgWrite: %s\n",getpid(),parent_msg);
		setup_flag(WFLAG);
		while(check_flag(RFLAG));
		clear_flag(WFLAG);
		clear_flag(DATAFILE);
	}
	printf("[%d] Done\n",getpid());
	return 0;
}
/*----------------------------------------------------------------------------*/

Notice that no error checking has been done for the flag and message management functions. This is left as an exercise for you. Next, let us take a look at the 'library' source:

fmsglib.c
/*----------------------------------------------------------------------------*/
#include "fmsglib.h"
/*----------------------------------------------------------------------------*/
#include <stdio.h>
/*----------------------------------------------------------------------------*/
int check_flag(char *pname)
{
	int status = FLAG_EXISTS;
	FILE *pfile = fopen(pname,"r");
	if(pfile) fclose(pfile);
	else status = FLAG_MISSING;
	return status;
}
/*----------------------------------------------------------------------------*/
int clear_flag(char *pname)
{
	int status = remove(pname);
	if(status<0)
		status = FLAG_UNTOUCHED;
	return status;
}
/*----------------------------------------------------------------------------*/
int setup_flag(char *pname)
{
	int status = check_flag(pname);
	if(status==FLAG_MISSING)
	{
		FILE *pfile = fopen(pname,"wt");
		if(pfile) fclose(pfile);
		else status = FLAG_SETFAILED;
	}
	else
	{
		status = FLAG_SETEXISTS;
	}
	return status;
}
/*----------------------------------------------------------------------------*/
int write_message(char *pname, char *message)
{
	int status = MSG_WRITE_SUCCESS;
	FILE *pfile = fopen(pname,"wt");
	if(pfile)
	{
		fprintf(pfile,message);
		fclose(pfile);
	}
	else status = MSG_WRITE_FAILED;
	return status;
}
/*----------------------------------------------------------------------------*/
int read_message(char *pname, char *message, int size)
{
	int status = MSG_READ_SUCCESS;
	FILE *pfile = fopen(pname,"rt");
	if(pfile)
	{
		int loop = 0, test;
		while((test=fgetc(pfile))!=EOF)
		{
			message[loop++] = (char) test;
			if(loop>=size-1)
			{
				status = MSG_READ_OVERFLOW;
				break;
			}
		}
		fclose(pfile);
		message[loop] = 0x0;
	}
	else status = MSG_READ_FAILED;
	return status;
}
/*----------------------------------------------------------------------------*/

The functions are pretty straight forward, using file access functions available in stdio.h. Notice that it uses a few constants that is defined in the header file:

fmsglib.h
/*----------------------------------------------------------------------------*/
#ifndef __CMSGLIBH__
#define __CMSGLIBH__
/*----------------------------------------------------------------------------*/
/** <function>_flag return values */
#define FLAG_EXISTS 0
#define FLAG_MISSING -1
#define FLAG_CLEARED 0
#define FLAG_UNTOUCHED -2
#define FLAG_SETUP 0
#define FLAG_SETEXISTS -3
#define FLAG_SETFAILED -4
/*----------------------------------------------------------------------------*/
/** message size limit */
#define MSG_SIZE_MAX 80
/** message write status */
#define MSG_WRITE_SUCCESS 0
#define MSG_WRITE_FAILED -1
/** message read status */
#define MSG_READ_SUCCESS 0
#define MSG_READ_FAILED -1
#define MSG_READ_OVERFLOW -2
/*----------------------------------------------------------------------------*/
int check_flag(char *pname);
int clear_flag(char *pname);
int setup_flag(char *pname);
int write_message(char *pname, char *message);
int read_message(char *pname, char *message, int size);
/*----------------------------------------------------------------------------*/
#endif
/*----------------------------------------------------------------------------*/

Finally, a makefile to help manage build:

makefile
# makefile to compile a c program
PROJECT = fmsg
OBJECTS = fmsglib.o $(PROJECT).o
CC = gcc
CFLAGS =
LDFLAGS =
$(PROJECT): $(OBJECTS)
	$(CC) $(CFLAGS) -o $@ $+ $(LDFLAGS)
%.o: %.c %.h
	$(CC) $(CFLAGS) -c $<
%.o: %.c
	$(CC) $(CFLAGS) -c $<
clean:
	rm -rf $(PROJECT) *.o

As with the previous 'long' code, spend some time to understand them. Try them out and modify them to test your knowledge.

Process: Things to Tinker

Thing 1 Write a program that create 5 separate child processes that executes in parallel (the example above creates a child process and wait for it to finish before creating another child). Each child waits for a random number of seconds (between 10 to 20) before exiting. The program must be able to detect all its child processes have finished before exiting.

Thing 2 Write a program that manages the listing, creation, deletion of child processes. Thus, when a child process is created, it should simply hang around until being told to quit (delete process). The listing of child processes should include information of its process ID and, optionally, its running time.

Thread: Introduction

Threads are parts of a running program that can be made to execute as if they are done in parallel. In this exercise, we will look at how a thread can be created and controlled. The codes in this section are specific to Linux platform and cannot be used on a Windows platform. There are equivalent functions in the Win32 API that can be used to achieve the same goal but they will NOT be discussed here.

Notes

The main difference between a thread and a process is the allocated address space. Unlike a process, a newly created thread shares the same address space as its parent (main thread). This is about as far as the difference goes because other than that, they are actually created to achieve the same thing - multitasking. In fact, in some operating systems like Linux, they are even treated/handled the same way.

The threads facility on Linux is implemented by an external POSIX threads (pthread) library and needs to be linked. So, remember to include the -pthread switch when linking codes using the threads facility.

Thread Creation

On Linux, the function that enables us to create a thread is called pthread_create. The prototype is

int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr,
  void *(*start_routine)(void*), void *restrict arg);

which is declared in pthread.h. It will return a value of 0 upon success (non-zero error number otherwise). You may want to read the man page (man pthread_create) to get information on the arguments.

In short, this function creates a thread (a task on Linux) with attributes specified in attr (default attributes will be used if this is NULL). If everything goes well, it will store the ID of the thread in thread and starts execution at the function pointed by start_routine with a single argument arg. The argument arg is actually very useful when you need to pass data structure to the newly created thread.

Simple Thread

A simple example that is equivalent to the code in Simple Fork:

smith_thread.c
#include <stdio.h>
#include <pthread.h>
 
static int check = 0;
 
void* go_smith(void* arg)
{
	if(arg)
	{
		pthread_t *psmith = (pthread_t*) arg;
		printf("I am Agent Smith %x @%p (%d)\n",*psmith,&check,check);
		check = 2;
		pthread_exit(0);
	}
	else
	{
		printf("I am Agent Smith 0 @%p (%d)\n",&check,check);
	}
	return 0x0;
}
 
int main(int argc, char *argv[])
{
	pthread_t smith;
	check = 1;
	pthread_create(&smith,0x0,&go_smith,&smith);
	pthread_join(smith,0x0);
	go_smith(0x0);
	return 0;
}

The output will be something like:

user@localhost:~$ smith_thread
I am Agent Smith 58956700 @0x600c80 (1)
I am Agent Smith 0 @0x600c80 (2)

Notice that when the global variable check is changed in the thread (check = 2;), this value is what gets printed out by the main thread. This shows that threads share the same address space as the main thread (notice that no parent-child concept here). You should note that pthread_join is equivalent to waitpid used earlier.

Thread vs Process

smith_thread.c
#include <stdio.h>
#include <pthread.h>
/* do not need stdlib.h */
/* do not need wait.h */
 
/* declare a global variable */
static int check = 0;
 
/* function to be executed by created thread */
void* go_smith(void* arg)
{
	pthread_t *psmith = (pthread_t*) arg;
	check++;
	printf("I am Agent Smith %u @%p (%d)\n",(unsigned)*psmith,&check,check);
	pthread_exit(0);
}
 
int main(int argc, char *argv[])
{
	pthread_t smith;
	check++;
	pthread_create(&smith,0x0,&go_smith,&smith);
	pthread_join(smith,0x0); /* wait for create thread to finish */
	printf("I am Agent Smith 0 @%p (%d)\n",&check,check);
	return 0;
}
smith_process.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
 
/* declare a global variable */
static int check = 0;
 
/* function to be executed by created process */
void* go_smith(void* arg)
{
	pid_t *psmith = (pid_t*) arg;
	check++;
	printf("I am Agent Smith %u @%p (%d)\n",(unsigned)*psmith,&check,check);
	exit(0);
}
 
int main(int argc, char *argv[])
{
	pid_t smith;
	check++;
	if(!fork()) { smith = getpid(); go_smith(&smith); }
	waitpid(smith,0x0,0); /* wait for create process to finish */
	printf("I am Agent Smith 0 @%p (%d)\n",&check,check);
	return 0;
}

Thread Control

This is actually much simpler due to the fact that threads share the same address space. But this also introduces race issues - what is the correct sequence when two threads are trying to access the same address? Therefore, a special flag is required to ensure only one thread can access a common address memory at one time, which will validate proper sequence of code. The flag is know as a MUTEX (MUTual EXclusive) and in the pthread library, it can be declared and initialized as follows:

pthread_mutex_t shared_region = PTHREAD_MUTEX_INITIALIZER;

PTHREAD_MUTEX_INITIALIZER is actually a macro that initializes a statically allocated mutex with default attributes.

The actual functions used to initialize and destroy a mutex are:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Whenever a thread requires access to the common address memory, it needs to request a lock on the mutex object using:

pthread_mutex_lock(&shared_region);

If the mutex is already locked by another thread, this function will block execution until it can get an exclusive lock. Of course, this means that all threads should be responsible enough to unlock the mutex once it is done:

pthread_mutex_unlock(&shared_region);

Multiple Threads

An example to show the use of mutexes:

parallel.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
/* statically allocated mutex initialization */
pthread_mutex_t shared_region = PTHREAD_MUTEX_INITIALIZER;
static int active = 0;
 
typedef struct __thread_data
{
	pthread_t thread_id;
	int thread_index;
	int thread_pause;
	int thread_exec;
	int thread_wait;
	int thread_run;
}
thread_data;
 
#include <unistd.h> /* STDIN_FILENO */
#include <termios.h> /* struct termios */
#define MASK_LFLAG (ICANON|ECHO|ECHOE|ISIG)
int getch(void)
{
	struct termios oldt, newt;
	int ch;
	tcgetattr(STDIN_FILENO, &oldt);
	newt = oldt;
	newt.c_lflag &= ~MASK_LFLAG;
	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
	ch = getchar();
	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
	return ch;
}
 
void* thread_exec(void* arg)
{
	if(arg)
	{
		thread_data *pdata = (thread_data*) arg;
		pdata->thread_run = 1;
		while(pdata->thread_exec)
		{
			if(pdata->thread_pause)
			{
				if(pdata->thread_run)
				{
					printf("[Thread-%d] Thread Paused!\n",pdata->thread_index);
					pdata->thread_run = 0;
				}
				continue;
			}
			if(!pdata->thread_run)
			{
				printf("[Thread-%d] Thread Resumes\n",pdata->thread_index);
				pdata->thread_run = 1;
			}
			printf("[Thread-%d] Sleeping %d seconds\n",pdata->thread_index,
				pdata->thread_wait);
			sleep(pdata->thread_wait);
			/* something happened while you were sleeping */
			if(!pdata->thread_exec) break;
			pthread_mutex_lock(&shared_region);
			printf("[Thread-%d] Changing active from %d to %d!\n",
				pdata->thread_index,active,pdata->thread_index);
			active = pdata->thread_index;
			pthread_mutex_unlock(&shared_region);
		}
		pthread_exit(0);
	}
	return 0x0;
}
 
int main(int argc, char *argv[])
{
	thread_data exec1, exec2;
	int not_done = 1, test;
	/* start random number generator */
	srand(time(0x0));
	/** initialize exec1 */
	exec1.thread_index = 1;
	exec1.thread_pause = 1;
	exec1.thread_exec = 1;
	exec1.thread_wait = (rand()%10)+1;
	exec1.thread_run = 0;
	pthread_create(&exec1.thread_id,0x0,&thread_exec,(void*)&exec1);
	/** initialize exec2 */
	exec2.thread_index = 2;
	exec2.thread_pause = 1;
	exec2.thread_exec = 1;
	exec2.thread_wait = (rand()%10)+1;
	exec2.thread_run = 0;
	pthread_create(&exec2.thread_id,0x0,&thread_exec,(void*)&exec2);
	/** main loop for main thread */
	sleep(1);
	printf("[Main] Starting threads execution...\n");
	exec1.thread_pause = 0;
	exec2.thread_pause = 0;
	while(not_done)
	{
		printf("[Main] <ESC> Exit, <SPACE> Pause/Resume, <R>andomize Sleep\n");
		switch(test=getch())
		{
			case 0x1B: /** ASCII ESC */
				not_done = 0;
				break;
			case 0x20: /** ASCII SPACE */
				exec1.thread_pause = !exec1.thread_pause;
				exec2.thread_pause = !exec2.thread_pause;
				break;
			case (int)'R':
				printf("[Main] Pause threads execution...\n");
				exec1.thread_pause = 1;
				exec2.thread_pause = 1;
				printf("[Main] Waiting for threads to pause...\n");
				while(exec1.thread_run||exec2.thread_run);
				printf("[Main] Randomizing Sleep Params...\n");
				exec1.thread_wait = (rand()%10)+1;
				exec2.thread_wait = (rand()%10)+1;
				printf("[Main] Wait1=%d, Wait2=%d\n",exec1.thread_wait,
					exec2.thread_wait);
				printf("[Main] Resume threads execution...\n");
				exec1.thread_pause = 0;
				exec2.thread_pause = 0;
				break;
			default:
				printf("[CHECK]=>'%02X'\n",test);
		}
	}
	/* stop threads */
	printf("[Main] Stopping threads execution...\n");
	exec1.thread_exec = 0;
	exec2.thread_exec = 0;
	/* wait threads to finish */
	printf("[Main] Waiting for threads to finish...\n");
	pthread_join(exec1.thread_id,0x0);
	pthread_join(exec2.thread_id,0x0);
	printf("[Main] Done!\n");
	return 0;
}

Another long one… you know what to do!

Thread: Things to Tinker

Thing1 Modify parallel.c so that the main thread can send a text message for the worker threads to print (or anything that is more interesting :-P). Hint: You should modify the structure so that it contains a buffer for message passing.

Thing 2 Write a program that manages the listing, creation, deletion of threads. Thus, when a thread is created, it should simply hang around until being told to quit (delete thread). The listing of running threads should include information of its thread ID and, optionally, its running time.

archive/pgt200lab01.txt · Last modified: 2020/09/13 17:24 by 127.0.0.1