Linux系统学习笔记:线程

Linux系统学习笔记:线程

主题: Linux系统学习笔记

« Linux系统学习笔记:信号

» Linux系统学习笔记:高级I/O

本篇总结POSIX线程。可以用多个线程在单进程环境中执行多个任务,这些线程都可以访问该进程的文件描述符和内存等资源。使用POSIX线程在编译和链接时需添加 -pthread 参数。

Contents

  • 线程
    • 重入
  • 线程控制
    • 创建线程
    • 线程属性
    • 终止线程
  • 线程同步
    • 互斥量
    • 读写锁
    • 条件变量
  • 线程私有数据
  • 使用线程的影响
    • 线程和I/O
    • 线程和fork
    • 线程和信号

线程

一般的进程可以看作只有一个线程,在同一时刻只做一件事。使用多线程后,每个线程都可以处理各自的任务。

使用线程的好处是:简化异步事件的处理,线程可以采用同步编程模式来处理事件;自动地共享内存和文件描述符;可以分解任务改善程序的吞吐量;对交互程序可以将输入输出和其他任务分开,改善响应时间。

线程包含表示进程内执行环境的必需信息,包括线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、 errno 变量、线程私有数据。进程的所有信息对进程的所有线程都是共享的,包括可执行程序文本、程序的全局内存和堆内存、栈、文件描述符。

线程具有线程ID,它只在所属进程内有效。和进程ID不同,线程ID可能实现为结构,所以必须用pthread_equal 函数来比较线程ID。

#include <pthread.h>

/* 比较线程ID
 * @return      相等返回非0值,否则返回0 */
int pthread_equal(pthread_t t1, pthread_t t2);

线程可以用 pthread_self 函数获取自身线程ID。

#include <pthread.h>

/* 返回线程ID */
pthread_t pthread_self(void);

重入

和信号处理程序类似,线程也有函数可重入问题。一个函数如果在同一时刻可以被多个线程安全调用,则它是线程安全的。大部分函数都是线程安全的,一些非线程安全的函数提供了 _r 后缀的版本,由调用者提供缓冲区代替静态的缓冲区来使函数变成线程安全的。注意函数对线程来说是可重入的并不等于对信号处理程序也是可重入的。

线程控制

创建线程

用 pthread_create 函数创建线程。

#include <pthread.h>

/* 创建线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

参数说明:

thread
pthread_create 成功返回时,  thread 指向的位置被设置为新创建线程的线程ID。
attr
设置线程的属性,使用  NULL 创建默认属性的线程。
start_routine
新创建线程从该函数的地址开始运行。
arg
传递给  start_routine 函数的参数。

新创建线程继承调用线程的浮点环境和信号屏蔽字,但未决信号集被清除。新线程和调用线程哪一个先运行是不确定的,它们之间同样会产生竞争。

线程属性

可以构建 attr 参数来修改线程属性的默认值。

#include <pthread.h>

/* 初始化和释放线程属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

主要的线程属性如下:

  • detachstate ,线程的分离状态属性。有两个可取值: PTHREAD_CREATE_DETACHED 以分离状态启动线程, PTHREAD_CREATE_JOINABLE 正常启动线程。后者程序可以获取线程的终止状态。
  • guardsize ,线程栈末尾的警戒缓冲区字节数。默认为 PAGESIZE 字节,设为0时会使警戒缓冲区机制无效。线程栈指针溢出到警戒区时,程序可能会收到信号。
  • stackaddr ,线程栈的最低地址。
  • stacksize ,线程栈的字节数。
#include <pthread.h>

/* 获取和设置detachstate
 * @return      成功返回0,出错返回错误编号 */
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/* 获取和设置guardsize
 * @return      成功返回0,出错返回错误编号 */
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
/* 获取和设置stackaddr和stacksize
 * @return      成功返回0,出错返回错误编号 */
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
/* 获取和设置stacksize
 * @return      成功返回0,出错返回错误编号 */
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

还有些线程属性不包含在 pthread_attr_t 结构中:

  • 并发度。控制用户级线程可以映射的内核线程或进程的数目,如果系统的实现是多对一的映射,增加可运行的用户级线程数可能会改善性能。并发度0表示由系统控制。
  • 可取消状态。默认为 PTHREAD_CANCEL_ENABLE 。取消选项控制 pthread_cancel 函数的行为,线程收到取消请求后会继续运行到取消点。设为 PTHREAD_CANCEL_DISABLE 时,取消请求会变为未决状态,直到可取消状态恢复默认值,才在下一个取消点取消。
  • 可取消类型。默认的取消类型为延迟取消 PTHREAD_CANCEL_DEFERRED ,即到达取消点取消。设为异步取消 PTHREAD_CANCEL_ASYNCHRONOUS 则线程可以在任意时间取消。
#include <pthread.h>

/* 返回当前并发度 */
int pthread_getconcurrency(void);
/* 设置并发度
 * @return      成功返回0,出错返回错误编号 */
int pthread_setconcurrency(int new_level);
/* 设置可取消状态
 * @return      成功返回0,出错返回错误编号 */
int pthread_setcancelstate(int state, int *oldstate);
/* 设置可取消类型
 * @return      成功返回0,出错返回错误编号 */
int pthread_setcanceltype(int type, int *oldtype);
/* 设置取消点 */
void pthread_testcancel(void);

终止线程

在进程的任一线程调用 exit 、 _Exit 、 _exit 函数,都会终止进程,把终止进程的信号发送到线程也会终止所在进程。

在不终止进程的情况下终止线程有三种方式:

  1. 线程从 start_routine 返回,返回值为线程的退出码。
  2. 线程调用 pthread_exit 函数。
  3. 线程被同一进程中的其他线程取消。

pthread_join 函数将阻塞线程,直到线程以上面三种方式之一终止。调用 pthread_join 会自动把线程置于分离状态。

#include <pthread.h>

/* 阻塞线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_join(pthread_t thread, void **retval);

如果线程是被取消的, retval 指向位置被置为 PTHREAD_CANCELED ,否则将包含返回码。可以将 retval 设为 NULL ,这样就不获取线程的终止状态。

pthread_exit 函数终止线程。

#include <pthread.h>

/* 终止线程 */
void pthread_exit(void *retval);

retval 可以被 pthread_join 函数访问到。

pthread_cancel 函数可以请求取消同一进程中的其他线程,它不等待线程终止。

#include <pthread.h>

/* 请求取消同一进程中的其他线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_cancel(pthread_t thread);

和进程类似,线程也可以设置退出时需要调用的函数,这些函数称为线程清理处理程序。线程调用pthread_exit 、响应取消请求或用非0参数调用 pthread_cleanup_pop 时,会调用处理程序,按和登记相反的顺序调用。线程按第1种方式终止的话,处理程序不会被调用。

pthread_cleanup_push 添加处理程序, pthread_cleanup_pop 删除处理程序。注意有个限制是它们必须在相同的作用域中配对使用。

#include <pthread.h>

/* 登记线程清理处理程序 */
void pthread_cleanup_push(void (*routine)(void *), void *arg);
/* 删除线程清理处理程序,execute为0时,不调用处理程序 */
void pthread_cleanup_pop(int execute);

线程的终止状态会保存到对该线程调用 pthread_join ,如果线程已经处于分离状态,则线程的存储资源在线程终止时立即收回。对分离状态的线程不能等待它的终止状态,对它调用 pthread_join 会产生失败,返回 EINVAL 。

pthread_detach 函数可以使线程进入分离状态。

#include <pthread.h>

/* 分离线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_detach(pthread_t thread);

例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "error.h"

void cleanup(void *arg)
{
    printf("cleanup: %s\n", (char *)arg);
}

void *thr_fn1(void *arg)
{
    printf("thread 1 start\n");
    pthread_cleanup_push(cleanup, "thread 1 first handler");
    pthread_cleanup_push(cleanup, "thread 1 second handler");
    printf("thread 1 push complete\n");
    if (arg)
        return((void *)1);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return((void *)1);
}

void *thr_fn2(void *arg)
{
    printf("thread 2 start\n");
    pthread_cleanup_push(cleanup, "thread 2 first handler");
    pthread_cleanup_push(cleanup, "thread 2 second handler");
    printf("thread 2 push complete\n");
    if (arg)
        pthread_exit((void *)2);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_exit((void *)2);
}

int main(void)
{
    int         err;
    pthread_t   tid1, tid2;
    void        *tret;

    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
        err_quit("can't create thread 1: %s\n", strerror(err));
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    if (err != 0)
        err_quit("can't create thread 2: %s\n", strerror(err));
    err = pthread_join(tid1, &tret);
    if (err != 0)
        err_quit("can't join with thread 1: %s\n", strerror(err));
    printf("thread 1 exit code %d\n", (int)tret);
    err = pthread_join(tid2, &tret);
    if (err != 0)
        err_quit("can't join with thread 2: %s\n", strerror(err));
    printf("thread 2 exit code %d\n", (int)tret);
    exit(0);
}

线程同步

考虑到进程的多个线程共享相同的内存,对共享的变量的修改可能会造成一致性问题。线程需要使用锁来使同一时间只允许一个线程访问变量。

互斥量

可以通过使用互斥量来确保同一时间只有一个线程访问数据。在访问共享资源之前对互斥量进行加锁,访问完成后释放互斥量上的锁。对互斥量加锁后,其他试图再次加锁的线程会被阻塞,释放互斥锁后,这些阻塞线程转为可运行状态,其中第一个可以对互斥量加锁,其他的线程则再次阻塞。

使用互斥变量前需要对它初始化,可以选择设为 PTHREAD_MUTEX_INITIALIZER 常量(静态分配)或用pthread_mutex_init 函数初始化。动态分配的互斥量在释放内存前需调用 pthread_mutex_destroy 。

#include <pthread.h>

/* 初始化和释放互斥量
 * @return      成功返回0,出错返回错误编号 */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

attr 设为 NULL 时用默认属性初始化互斥量。

有两个重要的互斥量属性:

  • 进程共享属性。默认为 PTHREAD_PROCESS_PRIVATE ,用于进程中多个线程同步对象。可以把同一内存区域映射到多个进程来共享数据,设置为 PTHREAD_PROCESS_SHARED 可以使从这一共享内存区域分配的互斥量用于这些进程的同步。
  • 类型属性。有四种类型: PTHREAD_MUTEX_NORMAL 为标准类型,不做错误检查和死锁检测;PTHREAD_MUTEX_ERRORCHECK 提供错误检查; PTHREAD_MUTEX_RECURSIVE 允许解锁前多次加锁;PTHREAD_MUTEX_DEFAULT 用来取默认语义,映射为 PTHREAD_MUTEX_NORMAL 。

相关的函数如下:

#include <pthread.h>

/* 初始化和释放互斥量属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
/* 获取和设置进程共享互斥量属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
/* 获取和设置类型互斥量属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

使用 pthread_mutex_lock 和 pthread_mutex_unlock 对互斥量加解锁。

#include <pthread.h>

/* 对互斥量加锁和解锁
 * @return      成功返回0,出错返回错误编号 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

若互斥量已经上锁, pthread_mutex_lock 会阻塞线程, pthread_mutex_trylock 则不阻塞线程,它失败并返回 EBUSY 。

一个线程试图多次对同一互斥量加锁会造成死锁。使用多个互斥量时,多个线程同时试图锁对方已经加锁的互斥量也会造成死锁。在处理多个互斥量时,需要注意加锁的顺序,必要时先释放已经占有的锁。

例:

#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(fp) (((unsigned long)fp)%NHASH)

struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;

struct foo {
    int             f_count; /* protected by hashlock */
    pthread_mutex_t f_lock;
    struct foo     *f_next; /* protected by hashlock */
    int             f_id;
    /* ... more stuff here ... */
};

struct foo *foo_alloc(void) /* allocate the object */
{
    struct foo  *fp;
    int         idx;

    if ((fp = malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            return(NULL);
        }
        idx = HASH(fp);
        pthread_mutex_lock(&hashlock);
        fp->f_next = fh[idx];
        fh[idx] = fp;
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        /* ... continue initialization ... */
    }
    return(fp);
}

void foo_hold(struct foo *fp) /* add a reference to the object */
{
    pthread_mutex_lock(&hashlock);
    fp->f_count++;
    pthread_mutex_unlock(&hashlock);
}

struct foo *foo_find(int id) /* find a existing object */
{
    struct foo  *fp;
    int         idx;

    idx = HASH(fp);
    pthread_mutex_lock(&hashlock);
    for (fp = fh[idx]; fp != NULL; fp = fp->f_next) {
        if (fp->f_id == id) {
            fp->f_count++;
            break;
        }
    }
    pthread_mutex_unlock(&hashlock);
    return(fp);
}

void foo_rele(struct foo *fp) /* release a reference to the object */
{
    struct foo  *tfp;
    int         idx;

    pthread_mutex_lock(&hashlock);
    if (--fp->f_count == 0) { /* last reference, remove from list */
        idx = HASH(fp);
        tfp = fh[idx];
        if (tfp == fp) {
            fh[idx] = fp->f_next;
        } else {
            while (tfp->f_next != fp)
                tfp = tfp->f_next;
            tfp->f_next = fp->f_next;
        }
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    } else {
        pthread_mutex_unlock(&hashlock);
    }
}

读写锁

读写锁也称为共享-独占锁,允许更高的并行性,它有三种状态:读模式加锁状态、写模式加锁状态、不加锁状态。同时只有一个线程可以占有写模式读写锁,同时可以多个线程占有读模式读写锁。适合于读次数远大于写的情况。

读写锁是写加锁状态时,任何试图加锁的线程都被阻塞;是读加锁状态时,可以以读模式加锁,但以写模式加锁的线程会被阻塞。为避免写模式加锁被饿死,写模式加锁被阻塞时会阻塞其他的读模式加锁请求。

读写锁的初始化和销毁和互斥量类似。

#include <pthread.h>

/* 初始化和释放读写锁
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

同样 attr 为 NULL 时使用默认属性。

读写锁有唯一的属性进程共享属性。和读写锁属性相关的函数有:

#include <pthread.h>

/* 初始化和释放读写锁属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
/* 获取和设置进程共享读写锁属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

分别使用 pthread_rwlock_rdlock 、 pthread_rwlock_wrlock 和 pthread_rwlock_unlock 函数来以读模式加锁、写模式加锁和解锁。

#include <pthread.h>

/* 以读模式加锁读写锁
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
/* 以写模式加锁读写锁
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
/* 解锁读写锁
 * @return      成功返回0,出错返回错误编号 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

和互斥量时一样, try 版本不阻塞线程,而是返回 EBUSY 。

例:

#include <stdlib.h>
#include <pthread.h>

struct job {
    struct job *j_next;
    struct job *j_prev;
    pthread_t   j_id;   /* tells which thread handles this job */
    /* ... more stuff here ... */
};

struct queue {
    struct job      *q_head;
    struct job      *q_tail;
    pthread_rwlock_t q_lock;
};

int queue_init(struct queue *qp)
{
    int err;

    qp->q_head = NULL;
    qp->q_tail = NULL;
    err = pthread_rwlock_init(&qp->q_lock, NULL);
    if (err != 0)
        return(err);
    /* ... continue initialization ... */
    return(0);
}

void job_insert(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    jp->j_next = qp->q_head;
    jp->j_prev = NULL;
    if (qp->q_head != NULL)
        qp->q_head->j_prev = jp;
    else
        qp->q_tail = jp;    /* list was empty */
    qp->q_head = jp;
    pthread_rwlock_unlock(&qp->q_lock);
}

void job_append(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    jp->j_next = NULL;
    jp->j_prev = qp->q_tail;
    if (qp->q_tail != NULL)
        qp->q_tail->j_next = jp;
    else
        qp->q_head = jp;    /* list was empty */
    qp->q_tail = jp;
    pthread_rwlock_unlock(&qp->q_lock);
}

void job_remove(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    if (jp == qp->q_head) {
        qp->q_head = jp->j_next;
        if (qp->q_tail == jp)
            qp->q_tail = NULL;
    } else if (jp == qp->q_tail) {
        qp->q_tail = jp->j_prev;
        if (qp->q_head == jp)
            qp->q_head = NULL;
    } else {
        jp->j_prev->j_next = jp->j_next;
        jp->j_next->j_prev = jp->j_prev;
    }
    pthread_rwlock_unlock(&qp->q_lock);
}

struct job *job_find(struct queue *qp, pthread_t id)
{
    struct job *jp;

    if (pthread_rwlock_rdlock(&qp->q_lock) != 0)
        return(NULL);
    for (jp = qp->q_head; jp != NULL; jp = jp->j_next)
        if (pthread_equal(jp->j_id, id))
            break;
    pthread_rwlock_unlock(&qp->q_lock);
    return(jp);
}

条件变量

条件变量给多个线程提供了一个会合的场所。条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定条件的发生。条件由互斥量保护,线程在改变条件状态之前必须先锁住互斥量。

条件变量使用前也必须初始化,可选择设为 PTHREAD_COND_INITIALIZER 常量(静态分配)或用pthread_cond_init 函数初始化。释放方式和互斥量类似。

#include <pthread.h>

/* 初始化和释放条件变量
 * @return      成功返回0,出错返回错误编号 */
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);

attr 设为 NULL 时用默认属性初始化条件变量。

条件变量也支持进程共享属性。和条件变量属性相关的函数有:

#include <pthread.h>

/* 初始化和释放条件变量属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
/* 获取和设置进程共享条件变量属性
 * @return      成功返回0,出错返回错误编号 */
int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

使用 pthread_cond_wait 函数来等待条件变为真。 pthread_cond_timedwait 函数可以指定等待的时间,时间到但条件未发生时返回 ETIMEDOUT 。

#include <pthread.h>

/* 等待条件发生
 * @return      成功返回0,出错返回错误编号 */
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
        const struct timespec *abstime);

mutex 对条件进行保护,调用时把锁住的 mutex 传给函数,函数以原子操作的方式解锁并检查条件变量,返回时再次加锁。 abstime 是绝对时间。

pthread_cond_signal 和 pthread_cond_broadcast 函数唤醒等待条件的线程,也称向线程或条件发送信号。一定要在改变条件状态后再给线程发送信号。

#include <pthread.h>

/* 唤醒等待条件的某个线程和所有线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

例:

#include <pthread.h>

struct msg {
    struct msg *m_next;
    /* ... more stuff here ... */
};

struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

void process_msg(void)
{
    struct msg *mp;

    for (;;) {
        pthread_mutex_lock(&qlock);
        while (workq == NULL)
            pthread_cond_wait(&qready, &qlock);
        mp = workq;
        workq = mp->m_next;
        pthread_mutex_unlock(&qlock);
        /* now process the message mp */
    }
}

void enqueue_msg(struct msg *mp)
{
    pthread_mutex_lock(&qlock);
    mp->m_next = workq;
    workq = mp;
    pthread_mutex_unlock(&qlock);
    pthread_cond_signal(&qready);
}

线程私有数据

线程私有数据是存储和查询与某个线程相关的数据的一种机制。有的时候希望线程可以独立地访问数据副本,而不用管和其他线程同步的问题。

进程中的所有线程都可以访问进程的整个地址空间,所以底层实现并不能阻止其他线程访问数据,只是通过管理线程私有数据的函数来实现。

线程私有数据机制使用键来关联数据。使用时先要创建键,然后关联数据。

使用线程的影响

线程和I/O

因为进程中的所有线程共享文件描述符,可以使用 pread 和 pwrite 函数来解决并发线程的读写问题。

线程和fork

线程调用 fork 创建子进程后,子进程继承整个地址空间的副本,也就同时继承了互斥量、读写锁和条件变量。但子进程中只有一个线程,即调用 fork 的线程,因此子进程可能没办法处理这些锁。如果马上调用exec 函数,地址空间被抛弃,就不必关心这些锁,否则的话需要清理这些锁。

可以使用 pthread_atfork 函数建立 fork 处理程序。

#include <pthread.h>

/* 建立fork处理程序,安装清理锁的函数
 * @return      成功返回0,出错返回错误编号 */
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

prepare 由父进程在 fork 创建子进程之前调用,获取父进程定义的所有锁。 parent 在 fork 创建子进程之后返回之前的父进程环境中调用,对 prepare 获取的所有锁解锁。 child 在 fork 返回之前的子进程环境中调用,也对 prepare 获取的所有锁解锁。

可以多次调用 pthread_atfork 来设置多套 fork 处理程序。

线程和信号

每个线程都有自己的信号屏蔽字,但进程中的所有线程共享信号处理方式。

在多线程进程中,线程需要使用 pthread_sigmask 函数代替 sigprocmask ,这两个函数行为相同。

#include <signal.h>

/* 修改线程的信号屏蔽字
 * @return      成功返回0,出错返回错误编号 */
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

把信号发送到线程,可以使用 pthread_kill 函数。

#include <signal.h>

/* 发送信号到线程
 * @return      成功返回0,出错返回错误编号 */
int pthread_kill(pthread_t thread, int sig);

设 sig 为0可以检查线程是否存在。

闹钟是进程资源,所有线程共享相同的 alarm 。

线程可以使用 sigwait 等待一个或多个信号的发生。调用它时如果指定的信号集有未决信号,则无阻塞返回,并移除全部未决信号。在函数执行期间会阻塞正在等待的信号。使用 sigwait 可以简化信号处理,以同步方式处理异步产生的信号。为防止信号中断线程,可以使用专用线程作信号处理。

#include <signal.h>

/* 等待信号发生
 * @return      成功返回0,出错返回错误编号 */
int sigwait(const sigset_t *set, int *sig);

set 为线程等待的信号集, sig 指向的整数会被设置为发送信号的数量。

例:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include "error.h"

int         quitflag;   /* set nonzero by thread */
sigset_t    mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;

void *thr_fn(void *arg)
{
    int err, signo;

    for (;;) {
        err = sigwait(&mask, &signo);
        if (err != 0)
            err_exit(err, "sigwait failed");
        switch (signo) {
        case SIGINT:
            printf("\ninterrupt\n");
            break;
        case SIGQUIT:
            pthread_mutex_lock(&lock);
            quitflag = 1;
            pthread_mutex_unlock(&lock);
            pthread_cond_signal(&waitloc);
            return(0);
        default:
            printf("unexpected signal %d\n", signo);
            exit(1);
        }
    }
}

int main(void)
{
    int         err;
    sigset_t    oldmask;
    pthread_t   tid;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        err_exit(err, "SIG_BLOCK error");
    err = pthread_create(&tid, NULL, thr_fn, 0);
    if (err != 0)
        err_exit(err, "can't create thread");
    pthread_mutex_lock(&lock);
    while (quitflag == 0)
        pthread_cond_wait(&waitloc, &lock);
    pthread_mutex_unlock(&lock);
    /* SIGQUIT has been caught and is now blocked; do whatever */
    quitflag = 0;
    /* reset signal mask which unblocks SIGQUIT */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
    exit(0);
}

你可能感兴趣的:(Linux系统学习笔记:线程)