26-Threads

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

26.1 Introduction

  • Old concurrent server: parent accepts the connection, forks a child, and the child handles the client. Problems with fork:
    1. fork is expensive.
      Memory is copied from the parent to the child and so on. Current implementations use copy-on-write, which avoids a copy of the parent’s data space to the child until the child needs its own copy.
    2. IPC is required to pass information between parent and child after fork.
      Passing information from parent to child before fork is easy, since the child starts with a copy of the parent’s data space and with a copy of all the parent’s descriptors. But, returning information from child to parent takes more work.
  • Threads are called lightweight processes and its creation can be 10-100 times faster than process creation.
  • All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but comes the problem of synchronization.
  • All threads within a process share the following:
    Process instructions
      Most data
      Open files(e.g., descriptors)
      Signal handlers and signal dispositions
      Current working directory
      User and group IDs
  • Each thread has its own
      Thread ID
      Set of registers, including program counter and stack pointer
      Stack(for local variables and return addresses)
      errno
      Signal mask
      Priority

26.2 Basic Thread Functions: Creation and Termination

pthread_create Function

#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
Returns: 0 if OK, positive EXXX value on error
  • When a program is started by exec, a single thread is created, called the initial thread or main thread. Additional threads are created by pthread_create.
  • Each thread within a process is identified by a thread ID, whose datatype is pthread_t(often an unsigned int). On successful creation of a new thread, its ID is returned through the pointer tid.
  • Each thread has many attributes: priority, initial stack size, whether it should be a daemon thread or not, and so on. When a thread is created, we can specify these attributes by initializing a pthread_attr_t variable that overrides the default. We specify the attr argument as a null pointer to take the default.
  • When we create a thread, we specify a function for it to execute. The thread starts by calling this function and then terminates either explicitly(by calling pthread_exit) or implicitly(by letting the function return). The address of the function is specified as func, and this function is called with a single pointer argument, arg. If we need multiple arguments to the function, we must package them into a structure and then pass the address of this structure as the single argument to the start function.
  • The function takes one argument, a generic pointer(void ), and returns a generic pointer(void ). This lets us pass one pointer(to anything we want) to the thread, and lets the thread return one pointer(to anything we want).
  • The return value from Pthread functions is 0 if successful or positive error indication on an error. Pthread functions do not set errno.

pthread_join Function

#include <pthread.h>
int pthread_join(pthread_t tid, void ** status);
Returns: 0 if OK, positive EXXX value on error
  • We can wait for a given thread to terminate by calling pthread_join.
    (pthread_create -> fork, pthread_join -> waitpid)
  • tid is the id of the thread that we want to wait for. There is no way to wait for any of our threads.
  • If the status pointer is non-null, the return value from the thread(a pointer to some object) is stored in the location pointed to by status.

pthread_self Function

#include <pthread.h>
pthread_t pthread_self(void);
Returns: thread ID of calling thread
  • Each thread has an ID that identifies it within a given process and it is returned by pthread_create. A thread fetches this value for itself using pthread_self.

pthread_detach Function

  • A thread is either joinable(the default) or detached. When a joinable thread terminates, its thread ID and exit status are retained until another thread calls pthread_join. A detached thread is like a daemon process: When it terminates, all its resources are released and we cannot wait for it to terminate. If one thread needs to know when another thread terminates, it is best to leave the thread as joinable.
#include <pthread.h>
int pthread_detach(pthread_t tid);
Returns: 0 if OK, positive EXXX value on error
  • pthread_detach changes the specified thread so that it is detached. It is commonly called by the thread that wants to detach itself, as in pthread_detach(pthread_self());

pthread_exit Function

#include <pthread.h>
void pthread_exit(void *status);
Does not return to caller
  • One way for a thread to terminate is to call pthread_exit. If the thread is not detached, its thread ID and exit status are retained for a later pthread_join by some other thread in the calling process. The pointer status must not point to an object that is local to the calling thread since that object disappears when the thread terminates.
  • Two other ways for a thread to terminate:
    1. The function that started the thread(the third argument to pthread_create) can return. Since this function must be declared as returning a void pointer, that return value is the exit status of the thread.
    2. If the main function of the process returns or if any thread calls exit, the process terminates, including any threads.

26.3 ‘str_cli’ Function Using Threads

unpthread.h header 1

  • unpthread.h header includes unp.h, followed by

26.4 TCP Echo Server Using Threads

Create thread 17-21

  • We call pthread_create when accept returns. The single argument to doit is the connected socket descriptor, connfd.
  • We cast the integer descriptor connfd to be a void pointer. This works only on systems on which the size of an integer is less than or equal to the size of a pointer.

Thread function 23-30

  • doit is the function executed by the thread. The thread detaches itself since there is no reason for the main thread to wait for each thread it creates. str_echo does not change from Figure 5.3. When this function returns, we must close the connected socket since the thread shares all descriptors with the main thread. With fork, the child did not need to close the connected socket because when the child terminated, all open descriptors were closed on process termination(Exercise 26.2).
  • The main thread does not close the connected socket, which we did with a concurrent server that calls fork. This is because all threads within a process share the descriptors, so if the main thread called close, it would terminate the connection. Creating a new thread does not affect the reference counts for open descriptors, which is different from fork.

Passing Arguments to New Threads

  • We cannot just pass the address of connfd to the new thread. That is, the following does not work.
int main(int argc, char **argv)
{
    int listenfd, connfd;
    ...
    for(;;)
    {
        len = addrlen;
        connfd = Accept(listenfd, cliaddr, &len);
        Pthread_create(&tid, NULL, &doit, &connfd);
    }
}

static void *doit(void *arg)
{
    int connfd;
    connfd = *((int *) arg);
    pthread_detach (pthread_self());
    str_echo(connfd);   /* same function as before */
    Close(connfd);      /* done with connected socket */
    return (NULL);
}
  • From ANSI C perspective this is acceptable: We are guaranteed that we can cast the integer pointer to be a void* and then cast this pointer back to an integer pointer. The problem is what this pointer points to.
  • There is one integer variable, connfd in the main thread, and each call to accept overwrites this variable with a new value(the connected descriptor). The following scenario can occur:
    1. accept returns, connfd is stored into(say the new descriptor is 5), and the main thread calls pthread_create. The pointer to connfd(not its contents) is the final argument to pthread_create.
    2. A thread is created and the doit function is scheduled to start executing.
    3. Another connection is ready and the main thread runs again(before the newly created thread). accept returns, connfd is stored into(say the new descriptor is now 6), and the main thread calls pthread_create.
  • Even though two threads are created, both will operate on the final value stored in connfd, which we assume is 6. The problem is that multiple threads are accessing a shared variable(the integer value in connfd) with no synchronization. In Figure 26.3, we solved this problem by passing the value of connfd to pthread_create instead of a pointer to the value. This is good given the way that C passes integer values to a called function(a copy of the value is pushed onto the stack for the called function).
  • Figure 26.4 shows a better solution to this problem.

17-22

  • Each time we call accept, we call malloc and allocate space for an integer variable, the connected descriptor. This gives each thread its own copy of the connected descriptor.

28-29

  • The thread fetches the value of the connected descriptor and then calls free to release the memory.
  • The malloc and free have been non-reentrant. Calling either function from a signal handler while the main thread is in the middle of one of these two functions has been a recipe for disaster, because of static data structures that are manipulated by these two functions. How can we call these two functions in Figure 26.4? POSIX requires that these two functions, along with many others, be thread-safe.

Thread-Safe Functions

  • POSIX.1 requires that all the functions defined by POSIX.1 and by the ANSI C standard be thread-safe, with the exceptions listed in Figure 26.5.

  • POSIX says nothing about thread safety with regard to the networking API functions.
  • All of the non-reentrant get? functions were summarized in Figure 11.21. We see from Figure 26.5 that the common technique for making a function thread-safe is to define a new function whose name ends in _r. Two of the functions are thread-safe only if the caller allocates space for the result and passes that pointer as the argument to the function.

26.5 Thread-Specific Data

26.6 Web Client and Simultaneous Connections(Continued)

26.7 Mutexes: Mutual Exclusion

26.8 Condition Variables

26.9 Web Client and Simultaneous Connections (Continued)

26.10 Summary

Exercises(Redo)

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章