A Thread is:
When two virtual processors are inhabiting the same address, threads make the machine look like a multiprocessor instead of a uniprocessor.
When a process or thread is forked, the new forked process or thread has both similar and different process descriptor properties from the original process and thread. These similarities and differences are shown in the table below. Depending on what part of the process descriptor is being forked, the new forked process or thread has either a new copy of the original, a shared copy of the original, or a brand new version different from the original.
Review:
Q: What are the three parts of the process descriptor?
A: - Accounting
- Abstract Machine
- Kernel Resources and Kernel State
Thread Descriptor : the kernel’s view of a thread (execution context). Each thread has a separate thread ID.
Q. What type of information is contained in the thread descriptor?
A. Statistics, registers, eip, esp, State, Kernel stack, wait queues
(All the entities that are indicated as copied or new in the above table)
In processes, everything is copied. With threads, as little is copied as possible. Copy- the new thread and the old thread have copies of the same thing, with each copy being independent of one another. Shared - the new thread and the old thread have access to the same thing, and and therefore dependent on each other.
Kernel stack – area reserved for responding to a system call. The kernel stack is where intermediate results can be stored. Wait Queue - If a process/thread makes a blocking system call, then that process/thread is going to go into a wait queue.
Q. Which of these objects are part of the execution context, and which is not?
A. Those that are copied - kernal stack, wait queue, etc...(refer to above table)
What does a system call look like? It is part of the execution context. Thus, they are not shared but are copied.
Accounting – The process ID is part of the Accounting Category.
What happens to the process ID when we fork a new process/thread?
Process Statistics- New for a new process/thread.
Q. What happens when you fork a process that has multiple threads?
A. Only the current thread is copied.
Idea: Can we make a version of fork() for threads?? This is a BAD IDEA in fact, as we can see by working through a hypothetical example. We propose a hypothetical system call called tfork(), for Thread Fork, which creates a new thread.
tfork() returns 0 in the child thread, and the child's thread ID in the parent thread.
Process creation: fork () or tfork()
Sample code:
int main (int c, char** v) {
int x;
if (tfork ( ) ==0) {
x = 2;
} else {
x = 3;
}
printf(“%d”, x);
With a regular fork, this code would print 32 or 23, since we aren't sure if the child or parent will run first. We would want tfork to do the same and print out 32 or 23, but it could possibly print out 33, 22, 32, or 23. What’s going on here? How does it print out 22 or 33?
PROBLEM
The stack is part of the execution context and allows us to have recursively called functions. Stacks are stored in the address space, but the address space in threads is always shared. Thus, we have two execution contexts that are touching the same variable on the stack. They have different registers, but they share the stack. Assuming x is stored on the stack, it is shared between the child and parent, since the stack is a shared address space. Therefore, the output is unpredictable. For example, the child thread sets x = 2, then goes to sleep, the parent then can set x = 3, consequently also changing the value of x in the child to 3. So in the end, our interface for creating a new thread was a BAD IDEA!
RECAP: Since stacks are part of the execution context in normal programming languages, when we create a new thread, we should also create a new stack.
Note: When you create a new process, it starts out with one new thread.
POSSIBLE SOLUTIONS
Below, is an example of Solution #2:
int main (int c, char **v) {
int x;
int *xp = &x;
if (tfork() == 0)
*xp=2;
else
*xp=3;
printf("%d",*xp);
int pthread_create (pthread_t * new_thread_id, pthread_attr_t *attr, void (*start)(void *), void *start_argument))
We supply a new function that is going to be called. We don’t continue running on the same function, but instead start on a new one. 1-Returns new threads id, 2-set of thread attributes (not talked about in class) 3-function that gets called on the new stack
void pthread_exit //exits a thread int pthread_join //equivalent of wait
It will be faster to fork threads vs processes, since the address spaces don't need to be copied.
MP3 player Version 1 (abstracted)
char buf [2048];
int main (int c, char "**x")
{
while (!feof(stdin))
{
read_frame(&buf[0]);
process_audio (&buf[0]);
process_video (&buf[0]);
}
}
//Problem: The audio will first be processed, and then the video will be processed. Thus, there is no synchronization.
MP3 player Version 2 (abstracted)
char buf [2048];
int main (int c, char **x)
{
while (!feof(stdin))
{
read_frame(&buf[0]);
if(fork( )==0)
process_audio (&buf[0]);
process_video (&buf[0]);
}
}
//Problem: Now, the problem is that the other thread was not exited.
MP3 player Version 3 (abstracted)
char buf [2048];
int main (int c, char **x)
{
while (!feof(stdin))
{
read_frame(&buf[0]);
if(fork( )==0)
{
process_audio (&buf[0]);
exit (0);
}
process_video (&buf[0]);
}
}
MP3 player Version 4 (abstracted)
char buf [2048];
int main (int c, char **x)
{
while (!feof(stdin))
{
pthread_t audio;
read_frame(&buf[0]);
if(fork( )==0)
{
process_audio (&buf[0]);
exit (0);
}
pthread_create (&audio, NULL, &process_audio, &buf[0]);
// The process_audio thread will exit when it’s done processing the frame.
// But when does the audio thread complete?
Process_video (&buf[0]);
}
}
//Problem: What happens if the video finishes processing before the audio finishes processing?
The changes the parent process makes to the buffer, are now visible to the child. Before with processes, you didn’t need to worry about buffers being changed underneath. However, now with threads, you do.
MP3 player Version 5 (abstracted)
char buf [2048];
int main (int c, char **x)
{
while (!feof(stdin))
{
pthread_t audio;
read_frame(&buf[0]);
if(fork( )==0)
{
Process_audio (&buf[0]);
exit (0);
}
pthread_create (&audio, NULL, &process_audio, &buf[0]);
process_video (&buf[0]);
pthread_join(audio, &status); //equivalent of waitpid
}
}
//Problem: This still has a lot of overhead. We want only want to create
one thread to process all the audio, and one thread to process all the video
MP3 player Version 6 (abstracted)
char buf [2048];
int main (int c, char **x)
{
pthread_t audio;
pthread_create (&audio, NULL, &process_audio, &buf[0]);
while ( ){
Read_frame(&buf[0]);
Process_video (&buf[0]);
}
}
Now, we don’t have pthread_join because we don’t want the audio thread to exit until all the frames have been processed. Instead, we want to do some kind of waiting (without exiting the thread). This sounds like something along the lines of what sleep does.
MP3 player Version 7 (abstracted)
char buf [2048];
int main (int c, char **x) {
pthread_t audio;
pthread_create (&audio, NULL, &process_audio, &buf[0]);
while ( ){
read_frame(&buf[0]);
frame_ready=1;
process_video (&buf[0]);
while(!audio_done) /* do nothing */;
audio_done=0; //reset value;
}
}
void process_audio (void *buf() ){
while(1) {
while(!frame_ready)/* */; ///busy waiting
frame_ready=0;
/*play audio*/
audio_done=1;
}
}
//PROBLEM: Busy waiting.
Example of a signal:
int sleep (int time) //blocks process for “time” seconds
int pthread_kill (pthread_t pt, int signo) //delivers this signal to this thread Signo: names this event. 321 event types: kill, segv, usr1 void signal (int singo, void (*sighandler)(int)); Supplies a handler for a signal when signal signo is delivered, Call the “sighandler” function
Generally, sighandlers generally just set a few flags; may make some system calls. CAUTION: Many operations are not safe for sighandlers; e.g. malloc,
Now that we know about signals, we can get rid of all busy waiting).
MP3 player Version 8 (abstracted)
char buf [2048];
int main (int c, char **x) {
pthread_t audio;
pthread_create (&audio, NULL, &process_audio, &buf[0]);
while ( ){
read_frame(&buf[0]);
frame_ready=1; Pthread_kill(audio, SIGUSR1);
process_video (&buf[0]);
while(!audio_done) /**/;
audio_done=0; //reset value;
//Implement sleep here
}
}
void process_audio (void *buf() ){
while(1)
{
void handler (int) { }
Signal (SIGUSR1, &handler);
while(!frame_ready)
/* sleep(infinite time)*/; ///busy waiting
frame_ready=0;
/*play audio*/
audio_done=1;
//Sends a signal here.
}
}
What happens when video calls audio? It will interrupt audio’s sleep.
Which of these things, needs special kernel support that is specific to threads?