apue2 阅读笔记--第12章

1. 线程属性

    什么东西都涉及到属性,程序里一涉及到属性的话,一般就是比较高级的要求了。比如socket的setsockopt函数比较重要。文件描述符的fcntl比较重要。同理,线程的:

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t   *attr);

Both return: 0 if OK, error number on failure

也是比较重要的。

线程属性的两个比较重要的方面,是否是分离状态和栈空间的大小,也有相应的函数进行设置。

#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,  int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); //设置是否分离状态

Both return: 0 if OK, error number on failure

小例子:

#include "apue.h"
#include <pthread.h>

int
makethread(void *(*fn)(void *), void *arg)
{
    int             err;
    pthread_t       tid;
    pthread_attr_t  attr;

    err = pthread_attr_init(&attr);
    if (err != 0)
        return(err);
    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (err == 0)
        err = pthread_create(&tid, &attr, fn, arg);
    pthread_attr_destroy(&attr);
    return(err);
}
#include <pthread.h>

int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);

int pthread_attr_setstack(const pthread_attr_t *attr, void *stackaddr, size_t *stacksize);  //设置栈空间地址和大小

 

#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

说明:The pthread_attr_setstacksize function is useful when you want to change the default stack size but don't want to deal with allocating the thread stacks on your own.

还可以设置线程的并发度等属性,但是这仅是对内核的一个请求。

2 线程同步

Just as threads have attributes, so too do their synchronization objects. In this section, we discuss the attributes of mutexes, readerwriter locks, and condition variables.

互斥量,读写锁,条件变量都可以设置"进程共享"属性

#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

Both return: 0 if OK, error number on failure

……

3线程可重入性

 

reentrant

信号处理可冲入函数的对比表

sig_reentrant

注意标准IO函数默认是线程安全的。

Figure 12.12. A reentrant (thread-safe) version of getenv
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

extern char **environ;

pthread_mutex_t env_mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&env_mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

int
getenv_r(const char *name, char *buf, int buflen)
{
    int i, len, olen;

    pthread_once(&init_done, thread_init);
    len = strlen(name);
    pthread_mutex_lock(&env_mutex);
    for (i = 0; environ[i] != NULL; i++) {
        if ((strncmp(name, environ[i], len) == 0) &&
          (environ[i][len] == '=')) {
            olen = strlen(&environ[i][len+1]);
            if (olen >= buflen) {
                pthread_mutex_unlock(&env_mutex);
                return(ENOSPC);
            }
            strcpy(buf, &environ[i][len+1]);
            pthread_mutex_unlock(&env_mutex);
            return(0);
        }
    }
    pthread_mutex_unlock(&env_mutex);
    return(ENOENT);
}
4.线程私有数据

Before allocating thread-specific data, we need to create a key to associate with the data. The key will be used to gain access to the thread-specific data. We use pthread_key_create to create a key.

#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
Returns: 0 if OK, error number on failure
 

The key created is stored in the memory location pointed to by keyp. The same key can be used by all threads in the process, but each thread will associate a different thread-specific data address with the key. When the key is created, the data address for each thread is set to a null value.

In addition to creating a key, pthread_key_create associates an optional destructor function with the key. When the thread exits, if the data address has been set to a non-null value, the destructor function is called with the data address as the only argument. If destructor is null, then no destructor function is associated with the key. When the thread exits normally, by calling pthread_exit or by returning, the destructor is called. But if the thread calls exit, _exit, _Exit, or abort, or otherwise exits abnormally, the destructor is not called.

We can break the association of a key with the thread-specific data values for all threads by calling pthread_key_delete.

#include <pthread.h>

int pthread_key_delete(pthread_key_t *key);

Returns: 0 if OK, error number on failure

Note that calling pthread_key_delete will not invoke the destructor function associated with the key. To free any memory associated with the key's thread-specific data values, we need to take additional steps in the application.

#include <pthread.h>

void *pthread_getspecific(pthread_key_t key);

Returns: thread-specific data value or NULL if no value
has been associated with the key

[View full width]

int pthread_setspecific(pthread_key_t key, const void *value);

Returns: 0 if OK, error number on failure

5.取消选项

Two thread attributes that are not included in the pthread_attr_t structure are the cancelability state and the cancelability type. These attributes affect the behavior of a thread in response to a call to pthread_cancel (Section 11.5).

The cancelability state attribute can be either PTHREAD_CANCEL_ENABLE or PTHREAD_CANCEL_DISABLE. A thread can change its cancelability state by calling pthread_setcancelstate.

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
Returns: 0 if OK, error number on failure

Recall from Section 11.5 that a call to pthread_cancel doesn't wait for a thread to terminate. In the default case, a thread will continue to execute after a cancellation request is made, until the thread reaches a cancellation point. A cancellation point is a place where the thread checks to see whether it has been canceled, and then acts on the request. POSIX.1 guarantees that cancellation points will occur when a thread calls any of the functions listed in Figure 12.14.

 

If your application doesn't call one of the functions in Figure 12.14 or Figure 12.15 for a long period of time (if it is compute-bound, for example), then you can call pthread_testcancel to add your own cancellation points to the program.

#include <pthread.h>

void pthread_testcancel(void);

When you call pthread_testcancel, if a cancellation request is pending and if cancellation has not been disabled, the thread will be canceled. When cancellation is disabled, however, calls to pthread_testcancel have no effect.

6. 注意线程有自己的信号处理函数

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

Returns: 0 if OK, error number on failure

#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);

Returns: 0 if OK, error number on failure

#include <signal.h>
int pthread_kill(pthread_t thread, int signo);

Returns: 0 if OK, error number on failure

7. 线程和fork

When a thread calls fork, a copy of the entire process address space is made for the child. Recall the discussion of copy-on-write in Section 8.3. The child is an entirely different process from the parent, and as long as neither one makes changes to its memory contents, copies of the memory pages can be shared between parent and child.

By inheriting a copy of the address space, the child also inherits the state of every mutex, readerwriter lock, and condition variable from the parent process. If the parent consists of more than one thread, the child will need to clean up the lock state if it isn't going to call exec immediately after fork returns.

Inside the child process, only one thread exists. It is made from a copy of the thread that called fork in the parent. If the threads in the parent process hold any locks, the locks will also be held in the child process. The problem is that the child process doesn't contain copies of the threads holding the locks, so there is no way for the child to know which locks are held and need to be unlocked.

This problem can be avoided if the child calls one of the exec functions directly after returning from fork. In this case, the old address space is discarded, so the lock state doesn't matter. This is not always possible, however, so if the child needs to continue processing, we need to use a different strategy.

To clean up the lock state, we can establish fork handlers by calling the function pthread_atfork.

#include <pthread.h>

int pthread_atfork(void (*prepare)(void), void (*parent)(void),
                   void (*child)(void));
Returns: 0 if OK, error number on failure

8. 线程与I/O

We introduced the pread and pwrite functions in Section 3.11. These functions are helpful in a multithreaded environment, because all threads in a process share the same file descriptors.

Consider two threads reading from or writing to the same file descriptor at the same time.

Thread A

Thread B

lseek(fd, 300, SEEK_SET);

lseek(fd, 700, SEEK_SET);

read(fd, buf1, 100);

read(fd, buf2, 100);

If thread A executes the lseek and then thread B calls lseek before thread A calls read, then both threads will end up reading the same record. Clearly, this isn't what was intended.

To solve this problem, we can use pread to make the setting of the offset and the reading of the data one atomic operation.

Thread A

Thread B

pread(fd, buf1, 100, 300);

pread(fd, buf2, 100, 700);

Using pread, we can ensure that thread A reads the record at offset 300, whereas thread B reads the record at offset 700. We can use pwrite to solve the problem of concurrent threads writing to the same file.

posted @ 2011-12-05 11:54  jialejiahi  阅读(452)  评论(0编辑  收藏  举报