APUE学习笔记(十二)线程控制

12. 1 线程属性

  1. 如果在创建线程时就知道不需要了解线程的终止状态,就可以使用pthread_attr_setdetachstate
  2. pthread_attr_init对线程属性进行初始化,pthread_attr_destroy执行清理工作。
  3. 对于进程来说虚地址空间的大小是固定的,但对于线程来说,同样大小的虚地址空间必须被所有的线程栈共享。如果线程栈的虚地址空间都用完了,用pthread_attr_setstack函数来改变新建线程的栈位置。
  4. 如果希望改变默认的栈大小但又不想自己处理线程栈的分配问题,使用pthread_attr_setstacksize函数就非常有用。
  5. 线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小

12. 2 同步属性

12.2.1 互斥量属性

进程共享属性、健壮属性以及类型属性。

1.进程共享属性

PTHREAD_PROCESS_PRIVATE  在进程中,多个线程可以访问同一个同步对象。
PTHREAD_PROCESS_SHARED   多个进程访问共享数据通常也需要同步

2.互斥量健壮属性与在多个进程间共享的互斥量有关。

PTHREAD_MUTEX_STALLED   持有互斥量的进程终止时不需要采取特别的动作
PTHREAD_MUTEX_ROBUST  返回EOWNERDEAD

3.类型属性:

PTHREAD_MUTEX_NORMAL     重复加锁会死锁
PTHREAD_MUTEX_ERRORCHECK  重复加锁返回错误
PTHREAD_MUTEX_RECURSIVE  可重入锁
PTHREAD_MUTEX_DEFAULT   未定义
12.2.2 读写锁属性

读写锁支持的唯一属性是进程共享属性,与互斥量的进程共享属性是相同的。

12.2.3 条件变量属性

进程共享属性和时钟属性。

12.2.4 屏障属性

目前定义的屏障属性只有进程共享属性,它控制着屏障是可以被多进程的线程使用,还是只能被初始化屏障的进程内的多线程使用。

12.3 重入

1. 线程安全:

如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是线程安全的。

实现方式包括:

  • 使用互斥量保护全局变量
  • 不适用全局变量

2. 可重入:

一个函数能够被多次安全调用,称为可重入的。

分为两种情况

  • 多个线程可以同时调用函数,称为对多个线程可重入。
  • 同一个线程因为信号处理程序多次调用同一个函数,称为对信号处理程序可重入。

实现方式:

  • 不使用全局变量,每个调用者传入自己的缓存区。

3. 两者的关系:

如果一个函数对多个线程来说是可重入的,就说这个函数就是线程安全的。但这并不能说明对信号处理程序来说该函数也是可重入的。

如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是异步信号安全的。

必须使用递归互斥量(可重入锁)保证线程安全,还要阻止来自信号处理程序的死锁。

例子:

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

extern char **environ;

pthread_mutex_t env_mutex;

static pthread_once_t init_once = 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_once, 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;
}

int main(void ) {
    char* name = "LOGNAME";
    char buf[1024];
    int err;

    if ((err = getenv_r(name, buf, 1024)) != 0)
        err_exit(err, "getenv error");

    printf("LOGNAME: %s\n", buf);
}

12.4 线程和信号

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

进程中的信号是递送到单个线程的。如果信号与硬件故障相关,那么一般会被发送到引起该事件的线程中去,而其他的信号
则被发送到任意一个线程。

如果多个线程在sigwait 的调用中因等待同一个信号而阻塞,那么在信号递送的时候就只有一个线程可以从 sigwait 中返回。

要把信号发送给进程可以调用kill,要把信号发送给线程可以调用pthread_kill。

闹钟定时器是进程资源,并且所有的线程共享相同的闹钟。

例子

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

sigset_t mask;
int quitflag;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t wait_loc = PTHREAD_COND_INITIALIZER;

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

    for(;;) {
        err = sigwait(&mask, &quitflag);   //线程等待mask中的信号
        if (err != 0)
            err_exit(err, "sigwait error");

        switch (signo) {
            case SIGINT:
                printf("quit");
                break;
            case SIGQUIT:
                pthread_mutex_lock(&lock);
                quitflag = 1;
                pthread_mutex_unlock(&lock);
                pthread_cond_signal(&wait_loc);
                return 0;
            default:
                printf("signo: %d", signo);
                exit(1);
        }

    }
}

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

    sigemptyset(&mask);
    sigaddset(&mask, SIGQUIT);  //设置信号屏蔽SIGQUIT,SIGINT
    sigaddset(&mask, SIGINT);
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        err_exit(err, "sigmask error");

    err = pthread_create(&tid, NULL, thr_fn, NULL);  //创建新线程
    if(err != 0)
        err_exit(err, "create error");

    pthread_mutex_lock(&lock);
    while (quitflag == 0)
        pthread_cond_wait(&wait_loc, &lock);  //等待条件满足
    pthread_mutex_unlock(&lock);

    quitflag = 0;

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0) //恢复原来的信号屏蔽字设置
        err_quit("sigprocmask error");

    exit(0);
}

12.5 线程和fork

当线程调用fork时,就为子进程创建了整个进程地址空间的副本。

在子进程内部只存在一个线程,是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁,子进程将同样占有这些锁。

在fork之后可能会出现同步状态不一致的问题。可以通过调用pthread_atfork函数建立fork处理程序,清除锁状态,

例子

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

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void prepare(void) {
    int err;

    printf("preparing lock....\n");
    if ((err = pthread_mutex_lock(&lock1)) != 0)
        printf("cannot lock1\n");
    if ((err = pthread_mutex_lock(&lock2)) != 0)
        printf("cannot lock2\n");
}

void parent(void) {
    int err;

    printf("parent unlock....\n");
    if ((err = pthread_mutex_unlock(&lock1)) != 0)
        err_cont(err, "parent cannot lock1\n");
    if ((err = pthread_mutex_unlock(&lock2)) != 0)
        err_cont(err, "parent cannot lock2\n");
}

void child(void) {
    int err;

    printf("child unlock....\n");
    if ((err = pthread_mutex_unlock(&lock1)) != 0)
        err_cont(err, "child cannot lock1\n");
    if ((err = pthread_mutex_unlock(&lock2)) != 0)
        err_cont(err, "child cannot lock2\n");
}

void* thrd_fn(void* arg) {
    printf("thread start\n");
    pause();
    return 0;
}

int main(void ) {
    int err;
    pid_t pid;
    pthread_t tid;

  //设置线程fork处理函数,分别是fork前,fork后父进程子进程执行的操作
    if ((err = pthread_atfork(prepare, parent, child)) != 0)  
        err_exit(err, "cannot install fork handler\n");
    if ((err = pthread_create(&tid, NULL, thrd_fn, NULL)) != 0)
        err_exit(err, "cannot create thread\n");

    sleep(2);
    printf("parent about fork....\n");
    if ((pid = fork()) < 0)
        err_sys("fork error\n");
    else if (pid == 0)
        printf("child return from fork");
    else
        printf("parent return from fork");

    exit(0);
}

12.6 线程与IO

pread和pwrite能够使偏移量的设定和数据的读取成为一个原子操作,适用于多线程环境。进程中的所有线程共享相同的文件描述符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值