线程控制

线程控制:

线程属性:
    #include <pthread.h>
    int pthread_attr_init(pthread_attr_t *attr);
    int pthread_attr_destroy(pthread_attr_t *attr);
        返回值:成功0,失败,错误编号
    让线程已开始就处于分离状态,可以调用pthread_attr_setdetachstate函数把线程属性detachstate
    设置为以下两个合法值之一:PTHREAD_CREATE_DETACHED,以分离状态启动线程;
    或者PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态
    #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);
        返回值:成功0,失败,错误编号

    一个以分离状态创建线程的函数:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.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);
    }
    可以在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR和_POSIX_THREAD_ATTR_STACKSIZE符号
    来检查系统是否支持每一个线程栈属性。如果定义了,就说明它支持相应的线程栈属性。也可以把参数传递
    给sysconf函数,检查运行时系统对线程栈属性的支持情况。

    可以使用函数pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理:
    #include <pthread.h>
    int pthread_attr_getstack(const pthread_attr_t *restrict attr,
            void **restrict stackaddr,
            size_t *restrict stacksize);
    int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);
        返回值:成功,0,否则,错误编号

    stackaddr线程属性被定义为栈的最低内存地址,但这并不一定是栈的开始位置。

    可以通过pthread_attr_getstacksize和pthread_attr_setstacksize函数读取或者设置线程属性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);
        返回值:成功0,失败,返回错误编号
    设置stacksize属性时,选择的stacksize不能小于PTHREAD_STACK_MIN.

    线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认值是由具体实现来定义的,但
    常用值是系统页大小。可以把guardsize线程属性设置为0,不允许属性的这种特征行为发生:在这种情况下,不会提供
    警戒缓冲区。同样,如果修改了线程属性stackaddr,系统就认为我们将自己管理栈,进而是栈警戒缓冲区机制无效,这
    等同于把guardsize线程属性设置为0.
    #include <pthread.h>
    int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);
    int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
        返回值:成功0,失败,返回错误编号

互斥量属性:
    对于非默认属性,可以用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来反初始化
    #include <pthread.h>
    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
        返回值:成功0,失败,返回错误编号
    3个属性是:进程共享属性、健壮属性以及类型属性。
    进行共享属性是可选的:检查系统是否定义了_POSIX_THREAD_PROCESS_SHARED符号来判断这个平台是否支持进程共享这个属性,也可以把
    _SC_THREAD_PROCESS_SHARED参数传给sysconf函数进行检查.

    可以使用pthread_mutexattr_getshared函数查询pthread_mutexattr_t结构,得到它的进程共享属性,使用
    pthread_mutexattr_setshared函数修改进程共享属性:
    #include <pthread.h>
    int pthread_mutexattr_getshared(const pthread_mutexattr_t *restrict attr,int *restrict pshared);
    int pthread_mutexattr_setshared(pthread_mutexattr_t *attr,int pshared);
        返回值:成功0,失败,返回错误编号

    使用pthread_mutexattr_getrobust函数获取健壮的互斥量的值。
    #include <pthread.h>
    int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,int *restrict robust);
    int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,int robust);
        返回值:成功0,失败,返回错误编号
    默认值是PTHREAD_MUTEX_STALLED,这意味着持有互斥量的进程终止时不需要采取特别的动作。
    另外一个值时PTHREAD_MUTEX_ROBUST,这个值将导致线程调用pthread_mutex_lock获取锁,而该锁被另一个进程持有,但它终止时并没有
    对该锁进行解锁,此是进程会阻塞,从pthread_mutex_lock返回的值为ROWNERDEAD而不是0.

    调用pthread_mutex_consistent函数,指明与该互斥量相关的状态互斥量解锁之前时一致的。
    #include <pthread.h>
    int pthread_mutex_consistent(pthread_mutex_t *mutex);
        返回值:成功0,失败,返回错误编号

    #include <pthread.h>
    int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
    int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);
        返回值:成功0,失败,返回错误编号


读写锁属性:
    #include <pthread.h>
    int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
    int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
        返回值:成功0,失败,返回错误编号
    #include <pthread.h>
    int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,int *restrict pshared);
    int pthread_rwlockattr_setpshared(pthread_rwlockatrr_t *attr,int pshared);
        返回值:成功0,失败,返回错误编号

条件变量属性:
    进程共享属性和时钟属性。
    #include <pthread.h>
    int pthread_condattr_init(pthread_condattr_t *attr);
    int pthread_condattr_destroy(pthread_conattr_t *attr);
        返回值:成功0,失败,返回错误编号
    #include <pthread.h>
    int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
    int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
        返回值:成功0,失败,返回错误编号
    #include <pthread.h>
    int pthread_condattr_getclock(const pthread_condattr_t *restrict attr,clockid_t *restrict clock_id);;
    int pthread_condattr_setclock(pthread_condattr_t *attr,clokid_t clock_id);
        返回值:成功0,失败,返回错误编号

屏障属性:
    #include <pthread.h>
    int pthread_barrierattr_init(pthread_barrierattr_t *attr);
    int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
        返回值:成功0,失败,返回错误编号
    #include <pthread.h>
    int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,int *restrict pshared);
    int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,int pshared);
        返回值:成功0,失败,返回错误编号
    进程共享属性的值可以是PTHREAD_PROCESS_SHARED(多进程中的多个线程可用),
    可以时PTHREAD_PROCESS_PRIVETE(只有初始化屏障的那个进程内的多个线程可用)。


重入:
    POSIX.1还提供了以线程安全的方式管理FILE对象的方法。可以使用glockfile和ftrylockfile获取给定FILE对象关联的锁。
    这个锁时递归的:当你占有这把锁的时候,还是可以再次获取该锁,而且不会导致死锁。虽然这种锁的具体实现并不规定,但要求所有
    操作FILE对象的标准I/O例程的动作行为必须看起来就像它们内部调用了flockfile和funlockfile。
    #include <stdio.h>
    int ftrylockfile(FILE *fp);
        返回值:成功,0,失败,返回非0数值
    void flockfile(FILE *fp);
    void funlockfile(FILE *fp);

    如果标准的I/O例程都获取它们各自的锁,那么在做一次一个字符的I/O时就会出现严重的性能下降。在这种情况下,需要对每一个字符
    的读写操作进行获取锁和释放锁的动作。为了避免这种开销,出现了不加锁版本的基于字符的标准I/O例程。
    #include <stdio.h>
    int getchar_unlocked(void);
    int getc_unlocked(FILE *fp);
        返回值:成功,返回下一个字符,若遇到文加尾或者出错,返回EOF
    int putchar_unlocked(int c);
    int putchar_unlocked(int c,FILE *fp);
        返回值:成功,返回c,失败,返回EOF


线程特定数据:
    1、有时候需要维护基于每个线程的数据。因为线程ID并不能保证是小而连续的整数,所以就不能简单地分配一个每线程数据数组,用线程
    ID作为数组的索引。即使线程ID确实是小而连续的整数,我们还希望有一些额外的保护,防止某个线程的数据与其他线程的数据混淆。
    2、它提供了基于进程的接口适应多线程环境的机制。一个很明显的实例就是errno。以前的接口(线程出现以前)把errno定义为进程
    上下文中全局可访问的整数。系统调用和库例程在调用或执行失败是设置errno,把它作为操作失败时的附属结果。为了让线程也能够
    使用那些原本基于进程的系统调用和库例程,errno被重新定义为线程私有数据。这样,一个线程做了重置errno的操作也不会影响进程
    中其他线程的errno值。

    在分配线程特定数据之前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。使用pthread_key_create创建一个
    键:
    #include <pthread.h>
    int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));
        返回值:成功0,失败,错误编号
    创建的键储存在keyp指定的内存单元内,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行
    关联。创建新键时,每个进程的数据地址设为空值。
    它可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经置为非空,那么析构函数就会调用,它的唯一参数
    时该数据地址。如果传入的析构函数为空,就表明没有析构函数与这个键关联。当线程调用pthread_exit或者线程执行返回。正常
    退出,析构函数就会被调用。如果线程调用了exit、_exit、_Exit或者abort,或者出现其他非正常的退出时,就不会调用析构函数。

    我们可以通过调用pthread_key_delete来取消键与线程特定数据值之间的关联关系。
    #include <pthread.h>
    int pthread_key_delete(pthread_key_t key);
        返回值:成功0,失败,错误编号。


    解决系统的线程之间的竞争调用pthread_once:
    #include <pthread.h>
    pthread_once_t initflag=PTHREAD_ONCE_INIT;
    int pthread_once(pthread_once_t *initflag,void (*initfn)(void));
        返回值:成功0,失败,错误编号
    initflag必须是一个非本地变量,而且初始化必须时PTHREAD_ONCE_INIT。


    键一旦创建成功,就可以通过pthread_setspecific函数把键和线程特定数据关联起来,
    可以通过pthread_getspecific函数获得线程特定数据的地址:
    #include <pthread.h>
    void *pthread_getspecific(pthread_key_t key);
        返回值:线程特定数据值,若没有值与该键关联,返回NULL
    int pthread_setspecific(pthread_key_t key,const void *value);
        返回值:成功0,失败,错误编号

取消选项:
    有两个线程属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型:
    着两个属性影响这线程在响应pthread_cancle函数时所呈现的行为。
    可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCEL_DISABLE。
    线程可以通过调用pthread_setcancelstate修改它的可取消状态:
    #include <pthread.h>
    int pthread_setcancelstate(int state,int *oldstate);
        返回值:成功,0,失败,返回错误编号。
    ptread_setcancelstate把当前的可取消状态设置state,把原来的可取消请求存储在由oldstate
    指向的内存单元。

    调用pthread_testcancel函数在程序中添加自己的取消点:
    #include <pthread.h>
    void pthread_testcancel(void);

    我们所描述的默认的取消类型也称为i推迟取消。调用pthread_cancel后,在线程到达取消点之前,并不会
    真正的取消。可以通过pthread_setcanceltype来修改取消类型。
    #include <pthread.h>
    int pthread_setcanceltype(int type,int *oldtype);
        返回值:成功0,失败,返回错误编号。
    pthread_setcanceltype函数把取消类型设置为type(类型参数可以是PTHREADCANCEL_DEFERRED,//拖延,延缓,推迟
    也可以是PTHREAD_CANCEL_ASYNCHRONOUS//异步的),把原来的取消类型返回到oldtype指向的整型单元。
    异步取消和推迟取消不同,因为使用异步取消时,线程可以在任意时间撤销,不是非得遇到取消点才能被取消。

线程和信号:
    进程使用sigprocmask函数来阻止信号发送。然而,sigprocmask的行为在多线程的进程中并没有定义,线程必须使用:
    #include <signal.h>
    int pthread_sigmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);
        成功0,失败,错误编号
    pthread_sigmask工作在线程中,而且失败时返回错误码,不再像sigprocmask中那样设置errno并返回-1.

    set参数包含线程用于信号屏蔽字的信号集

    how参数可以取下列值之一:SIG_BLOCK,把信号集添加到线程信号屏蔽字中
    SIG_SETMASK,用信号集替换线程的信号屏蔽字
    SIG_UNBLOCK,从线程信号屏蔽字中移除信号集

    oset参数不为空,线程之前的信号屏蔽字就存储在它指向的sigset_t结构中

    线程可以通过把set参数设置为NULL,并把oset参数设置为sigset_t结构的地址,来获取当前的信号屏蔽字。这种情况下how就会
    被忽略。
    线程可以通过调用sigwait等待一个或者多个信号的信号:
    #include <signal.h>
    int sigwait(const sigset_t *restrict set,int *restrict signop);
        返回值:成功0,失败,错误编号
    set参数指定了线程等待的信号集。返回时,signop指向的整数将包含发送信号的数量。

    要把信号发送给进程,可以调用kill。要把信号发送给线程,可以调用pthread_kill:
    #include <signal.h>
    int pthread_kill(pthread_t thread,int signo);
        返回值:成功0,失败,返回错误编号
    可以传一个0值的signo来检查线程是否存在。如果信号的默认处理动作时终止该进程,那么把信号传递给某个线程仍然会杀死
    整个进程。

    注意,闹钟定时器时进程资源,并且所由的线程共享相同的闹钟。所以,进程的多个线程不可能不干扰地使用闹钟定时器。

    程序,我们等待信号处理程序设置标志表明主程序应该退出。唯一可运行的控制线程就是主线程和信号处理程序,所以阻塞信号足以
    避免错失标志修改。在线程中,我们需要使用互斥量来保护标志。
        #include <stdlib.h>
        #include <stdio.h>
        #include <unistd.h>
        #include <pthread.h>
        #include <signal.h>

        int quitflag;
        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)
                        printf("sigwait failed:%d",err);
                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)
                printf("SIG_BLOCK error:%d\n",err);

            err=pthread_create(&tid,NULL,thr_fn,0);
            if(err!=0)
                printf("can't create thread:%d\n",err);

            pthread_mutex_lock(&lock);
            while(quitflag==0)
            {
                pthread_cond_wait(&waitloc,&lock);
            }
            pthread_mutex_unlock(&lock);

            quitflag=0;

            if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
                printf("SIG_SETMASK error\n");
            exit(0);
        }
    执行:
    $ ./example5
    ^C
    interrupt
    ^C
    interrupt
    ^C
    interrupt
    ^C
    interrupt
    ^C
    interrupt
    ^C
    interrupt
    ^\


线程和fork:
    清理锁状态,可以通过pthread_atfork函数建立fork处理程序。
    #include <pthread.h>
    int pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));
        返回值:成功0,失败,错误编号。

线程和I/O:
    pread和pwrite函数。这些函数在多线程环境下是非常有用的,因为进程中的所有线程共享相同的文件描述符。

    考虑两个线程,在同一时间对同一个文件描述符进行读写操作。
        线程A             线程B 
    lseek(fd,300,SEEK_SET);         lseek(fd,700,SEEK_SET);
    read(fd,buf1,100);          read(fd,buf2,100);
    如果线程A执行lseek然后线程B在线程A调用read之前调用lseek,那么两个线程最终会读取同一条记录。很显然这不是我们
    希望的。
    为了解决这个问题,可以使用pread,使偏移量的设定和数据的读取称为一个原字操作。
        线程A             线程B
    pread(fd,buf1,100.300);         pread(fd,buf2,100.700);
    使用pread可以确保线程A读取偏移量为300的记录,而线程B读取偏移量为700的记录。
    可以使用pwrite来解决并发线程对同一文件进行写操作的问题。
posted @ 2018-04-02 20:54  带头大哥小白  阅读(115)  评论(0编辑  收藏  举报