• Operating System: Three Easy Pieces --- Thread API (Note)


    This chapter briefly covers the main properties of the thread API. Each part will be explained

    further in the subsequent chapters, as we know how to use the API. More details can be found

    in various books and online sources. We should note that the subsequent chapters introduces

    the concepts of locks and condition variables more slowly, with many examples; this chpater is

    thus better used as a reference.

                  Crux: How to Create and Control Threads

    What interface should the OS present for thread creation and control ? How should these 

    interfaces be designed to enable ease use of as well as utility?

                       Thread Creation

    The first thing you have to be aboe to write a multi-threaded program is to create new threads,

    and thus some kind of thread creation interface must exist. In POSIX, it is easy:

    #include <pthread.h>
    int pthread_create(pthread_t* thread, const pthread_attr_t* attr.
                                void* (*start_routine)(void*), void* arg);                            

    This declaration might look a little complex (particularly if you haven't used function pointers in

    C), but actually it's not too bad. There are four arguments: thread, attr, start_routines, and arg.

    The first, thread, is a pointer to a structure of type pthread_t; we will use this structure to 

    interact with this thread, and thus we need to pass it to pthread_create() in order to initialize it.

    The second argument, attr, is used to specify any attributes this thread might have. Some

    example include setting the stack size or perhaps information about the scheduling priority of

    the thread. An attribute is initialized with a separate call to pthread_attr_init(); see the mannual

    page for details. However, in most cases, the defaults will be fine; in this case, we will simply 

    pass the value NULL in.

    The third argument is the most complex, but is really just asking: which function should this

    thread start running in? In C, we call this a function pointer, and this one tells us the following

    is expected: a function name (start_routine), which is passed a single argument of type void*

    (as indicated in the parenthese after start_routine), and which returns a valur of type void*.

    If this routine instead required an integer argument, instead of a void pointer, the declaration

    would look like this:

    int pthread_create(...,
                               void* (*start_routine)(int),
                               int arg);
                                   

    If instead the routine took a void pointer as an argument, but returned an integer, it would look

    look this:

    int pthread_create(...,
                                int (*start_routine)(void*),
                                void* arg);

    Finally, the fouth argument, arg, is exactly the argument to be passed to the function where

    the thread begins execution. You might ask: why do we need these void pointers? Well, the

    answer is quite simple: having a void pointer as an argument to the function start_routine allows

    us to pass in any type of argument; having it as a return value allows the thread to return any

    type of result.

    Let's look at an example. Here we just create a thread that is passed two arguments, packaged

    into a single type we define ourselves. The thread, once created, can simply cast its argument to

    the type it expects and thus unpack the arguments as desired.

    And there it is! Once you create a thread, you really ahve another live executing entity, complete

    with its own call stack, running within the same address space as all the currently existing 

    threads in the program. The fun thus begins!

    #include <pthread.h>
    
    typedef struct __myarg_t {
          int a;
          int b;
    } myarg_t;
    
    void* mythread(void* arg) {
          myarg_t* m = (myarg_t*)arg;
          printf("%d %d
    ", m->a, m->b);
     
          return NULL;
    }
    
    int main(int argc, char* argv[]) {
         
          pthread_t p;
          int rc;
     
          myarg_t args;
          args.a = 10;
          args.b = 20;
          rc = pthread_create(&p, NULL, mythread, &arg);
          ......
    }

                        Thread Completion

    The example above shows how to create a thread. However, what happens if you want to wait

    for a thread to complete? You need to do something special in order to wait for completion;

    in particular, you must call the routine pthread_join(). 

    int pthread_join(pthread_t thread, void** value_ptr);

    This routine takses two arguments. The first is of type pthread_t, and is used to specify which

    thread to wait for. This variable is initialized by the thread creation routine (when you pass a

    pointer to it as an argument to pthread_create()); if you keep it around, you can use it to wait

    for that thread to terminate.

    The second argument is pointer to the return value you expect to get back. Because the routine

    can return anything, it is defined to return a pointer to void; because the pthread_join() routine

    changes the value of the passed in argument, you need to pass in a pointer to that value, not

    just the value itself.

    Let's look at another example. In the code, a single thread is again created, and passed a couple

    of arguments via the myarg_t structure. To return values, the myret_t type is used. Once the

    thread is finished running, the main thread, which has been waiting inside of the pthread_join()

    routine, then returns, and we can access the values returned from the thread, namely is in

    myarg_t.

    Second, if we are just passing in a single value (e.g., an int), we don't have to package it up

    as an argument. Figure 27.3 shows an example. In this case, life is a bit simpler, as we don not

    have to package arguments and return values inside of functions.

    Third, we should note that one has to be extremely careful with how values are returned from

    a thread. In particular, never return a pointer which refers to something allocated on the thread's

    call stack. If you do, what do you think will happen? (think about it!) Here is an example of a 

    dangerous piece of code, modified from the exampe in Figure 27.2.

    void* mythread(void* arg) {
         myarg_t*  m = (myarg_t*)arg;
         printf("%d %d
    ", m->a, m->b);
         myret_t r;
         r.x = 1;
         r.y = 2;
         
         return (void*)&r;
    }

    In this case, the variable r is allocated on the stack of mythread. However, when it returns, the

    value is automatically deallocated (that's why the stack is so easy to use, after all!), and thus

    passing back a pointer to a now deallocated variable will lead to all sorts of bad results. 

    Certainly, when you print out the values you think you returned, you will probably (but not

    necessarily!) be surprised. Try it and find out for yourself.

    Finally, you might notice that the use of pthread_create() to create a thread, followed by an 

    immediate call to pthread_join(), is a pretty strange way to create a thread. In fact, there is an

    easier way to accomplish this exact task; it is called a procedure call. Clearly, we will usually be

    creating more than just one thread and waiting for it to complete, otherwise there is not much

    purpose to using threads at all.

    We should note that all code that is multi-threaded uses the join routine. For example, a 

    multi-threaded web server might create a number of worker threads, and then use the main 

    thread to accept requests and pass them to the workers, indefinitely. Such long-lived programs

    thus may not need to join. However, a parallel program that creates threads to execute a

    particular task in parallel will likely use join to make sure all such work completes before exiting

    or moving onto the next stage of computation.

  • 相关阅读:
    OSPF 开放最短路径优先协议
    RIP 路由算法
    原创 记一个上门洗车服务范围的需求实现
    转载 一位资深程序员大牛给予Java学习者的学习路线建议
    原创 解决异步调用实时跳转
    FIFO队列 ADT接口 数组实现
    FIFO队列 ADT接口 链表实现
    约瑟夫问题 链表实现
    合并-查找接口实现
    快速查找 快速合并 加权快速合并 路径等分加权快速合并 算法
  • 原文地址:https://www.cnblogs.com/miaoyong/p/4966134.html
Copyright © 2020-2023  润新知