zoukankan      html  css  js  c++  java
  • Chapter 12 线程控制

    1.线程限制

    用sysconf函数可以获得和thread相关的一些系统信息,主要是线程相关的一些最大值:

    线程限量和sysconf的名字参数
    限量名描述名字参数
    PTHREAD_ DESTRUCTOR_ITERATIONS 当一个线程退出时一个实现将尝试销毁线程相关数据的最大次数。 _SC_THREAD_ DESTRUCTOR_ITERATIONS
    PTHREAD_ KEYS_MAX 一个进程可以创建的关键字的最大数量。 _SC_THREAD_ KEYS_MAX
    PTHREAD_ STACK_MIN 可以作为一个线程栈的最少字节数。 _SC_THREAD_ STACK_MIN
    PTHREAD_ THREADS_MAX 一个进程可以创建的最大线程数 _SC_THREAD_ THREADS_MAX

    虽然标准定义了这些常量,不过在很多系统上面可能根本就没有定义对应的限制符号(如_SC_THREAD_DESTRUCTOR_ITERATIONS可能未定义),或者sysconf函数返回错误。因此在很多时候这些很难派上用场

    2.线程属性

    1).前面讲到pthread_create等函数的时候,这些函数有一个参数pthread_attr_t。缺省情况下可以传NULL。但是如果想自己定义线程的相关属性的话,应该调用pthread_attr_init函数来定义:

    #include <pthread.h>
    
    int pthread_attr_init(pthread_attr_t *attr);
    int pthread_attr_destroy(pthread_attr_t *attr);
    //成功返回0,失败返回错误号

    pthread_attr_init函数负责初始化pthread_attr_t结构为缺省值。pthread_attr_destroy负责释放在pthread_attr_init函数调用时分配的内存,同时pthread_attr_destroy将会用无效值初始化属性对象,所以如果它被误用,pthread_create会返回一个错误。

    2).

    POSIX.1线程属性
    名字描述FreeBSD 5.2.1Linux 2.4.22Mac OS X 10.3Solaris 9
    detachstate 分离的线程属性 * * * *
    guardsize 在线程栈末尾的保卫缓冲的字节尺寸   * * *
    stackaddr 线程栈的最低地址 ob * * ob
    stacksize 线程栈的字节尺寸 * * * *

    注:线程都拥有自己的栈

    a.如果在创建这个线程的时候知道不需要线程的终止状态,,通过修改pthread_attr_t结构体里的detachstate属性,使线程以分离状态启动。可以使用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,失败返回错误号。

    可以调用pthread_attr_getdetachstate来得到当月的detachstate属性。被第二个参数指向的整型被设置为PTHREAD_CREATE_DETACHED或PTHREAD_CREATE_JOINABLE,取决于给定的pthread_attr_t结构体的这个属性的值。

    b.查询和修改线程栈属性的一般通过较新的函数pthread_attr_getstack和pthread_attr_setstack来进行。这些函数去除了更老的接口定义里的歧义。

    #include <pthread.h>
    
    int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restric stacksize);
    int pthread_attr_setstack(pthread_attr_t *attr, void *statckaddr, size_t *stacksize);
    //两者成功返回0,失败返回错误号。

    这两个函数可以用于管理stackaddr线程属性,也可以用于管理stacksize线程属性,如果线程栈用完了虚拟进程空间,那么调用malloc或mmap(14.9节)来为一个代替的栈分配空间,可以调用pthread_attr_setstack和pthread_attr_getstack来获得/设置线程的栈位置

    c.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_setstackszie(pthread_attr_t *attr, size_t stacksize);
    //两者成功返回0,失败返回错误号。

    d.线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小,缺省情况下这个大小正好是一个页=PAGESIZE。甚至可以用函数将该数值设置为0来禁止这个功能,如果我们修改了栈地址的话,系统会认为我们会自己处理溢出的问题,因此也不会提供这个功能。调用pthread_attr_get_guardsize和pthread_attr_set_guardsize可以获得/设置这个值

    #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,失败返回错误号。


    e.更多线程属性

    如果具体操作系统实现是按照1对1,也就是一个用户模式线程对应一个内核模式线程的话,那么修改这个值没有作用。但是如果操作系统实现用少量内核模式线程/进程来模拟用户模式线程的话,那么修改这个值可能会提高或者降低程序和系统的性能。Level值并没有具体的意义,只是一个hint。Level=0表示让系统自动选择。pthread_setconcurrency函数可以提示系统,表明希望的并发度,函数原型如下:

    #include <pthread.h>
    
    int pthread_getconcurrency(void);
    //返回当前并发级数。
    
    int pthread_setconcurrency(int level);
    //成功返回0,失败返回错误号。

    pthread_getconcurrency函数返回当前并发度。如果操作系统正控制着并发度(也就是说,之前没有调用过pthread_setconcurrency),那么pthread_getconcurrency将返回0

    3.同步属性

    就像线程具有属性一样,线程的同步对象也有属性,下面列出互斥量、读写锁和条件变量的属性

    1).互斥量属性

    a.使用pthread_mutexattr_init来初始化一个phread_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,失败返回错误号。

    b.可以使用pthread_mutexattr_getpshared函数来查询一个pthread_mutexattr_t结构体来得到进程共享属性。我们可以用pthread_mutexattr_setpshared函数改变进程共享属性。

    #include <pthread.h>
    
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
    int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
    //两者成功返回0,失败返回错误号。

    进程共享互斥体属性允许pthread库提供更高效的实现,当属性被设为PTHREAD_PROCESS_PRIVATE时,这是多线程应用的默认情况

    c.可以用pthread_mutexattr_gettype来得到互斥量类型属性,pthread_mutexattr_settype来修改互斥量属性。

    #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,失败返回错误号。
    互斥量类型行为
    互斥量类型未解锁时重新加锁?当不被拥有时解锁?当无锁时解锁?
    PTHREAD_MUTEX_NORMAL 死锁 无定义 无定义
    PTHREAD_MUTEX_ERRORCHECK 返回错误 返回错误 返回错误
    PTHREAD_MUTEX_RECURSIVE 允许 返回错误 返回错误
    PTHREAD_MUTEX_DEFAULT 无定义 无定义 无定义

    2).读写锁属性

    读写锁和互斥量互斥,也有属性。用pthread_rwlockattr_init来初始化一个pthread_rwlockattr_t结构体,用pthread_rwlockattr_destroy来回收这个结构体。

    #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_rwlockattr_t *attr, int pshared);
    //两者成功返回0,失败返回错误号。

    3).条件变量属性

     条件变量也与属性,与互斥量和读写锁类似

    #include <pthread.h>
    
    int pthread_condattr_init(pthread_condattr_t *attr);
    int pthread_condattr_destroy(pthread_condattr_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, 失败返回错误号。

    4.重入

    1).大部分Single UNIX Specification所定义的函数都是线程安全的,但是也有不少例外。实际使用的时候建议参考文档,确定函数是否是线程安全。

    2.)文件支持用ftrylockfile, flockfile, funlockfile来锁定文件访问。标准IO函数被要求必须调用在内部实现中调用flockfile, funlockfile。基于字符的部分IO函数具有非线程安全版本,以_unlocked结尾,如:getchar_unlocked, getc_unlocked, putchar_unlocked, putc_unlocked

    3).书中提供了一个可重入的getenv_r实现。要点是:

    a.用到了Recursive Mutex(使用pthread_mutexattr_settype函数调用设置)来保护自己和其他线程冲突(普通的Mutex就可以做到),同时允许重入(必须用Recursive Mutex)

    b.要求调用者提供自己的buffer,而不是用静态全局变量envbuf来访问结果

    c.使用pthread_once函数保证只调用一个初始化函数一遍,用于初始化Mutex(当然用其他方法也可以)

     

     

    5.线程私有数据

    • 线程私有数据是一种很方便的将数据和线程联系起来的方法,在C Runtime中也大量用到线程私有数据来维护线程相关的数据,一个典型的例子是errno:实际上errno是一个函数调用,返回和线程相关的错误值。Windows中有类似的机制,称为TLS (Thread Local Storage)
    • 访问线程私有数据需要使用Key。不同线程使用同一个key访问同一类型的数据(比如Errno),但是可以存放不同的值。Key的类型为pthread_key_t
    • 用pthread_key_create函数创建key

    1).在分配线程偶数据之前,需要创建与该数据相关联的键,这个键将用于获取对线程私有数据的访问权,使用pthread_key_create函数创建key

    #include <pthread.h>
    
    int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
    //成功返回0,失败返回错误号。

    创建的关键字被存储在keyp所指的内存单元。这个键可以被进程里所有线程使用,但是每个线程将把这个键关联于一个不同的线程特定数据地址。当创建字被创建时,每个线程的数据地址被设为null值。

    一般情况下,这个destructor用来销毁用户用malloc为线程私有数据分配的空间。注意:一般不应该用destructor来调用pthread_key_delete,因为delete对于一个key只用调一次,而destructor是对每个线程都调用的,前提是线程正常退出并且TSD不为NULL。

    Key的总数量可能会有限制。可以用PTHREAD_KEYS_MAX来查询最大值。因为调用析构函数的时候这个析构函数可能又会创建新的Key,所以当线程退出的时候,调用Destructor的过程会反复继续直到没有key具有非NULL值或者次数到达最大值PTHREAD_DESTRUCTOR_ITERATIONS为止。这个值可以用sysconf获得。

    2).对于所有线程,都可以通过调用pthread_key_delete来取消与线程私有数据值之间的关联

    #include <pthread.h>
    
    int pthread_key_delete(pthread_key_t *key);
    //成功返回0,失败返回错误号。

    注意调用pthread_key_delete并不会激活与键关联的析构函数,要释放任何与key对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。

    3).有些线程可能看到一个关键字值,而另一个线程可能看到一个不同的值,这取决于系统如果调度线程。解决这个竞争的方法是使用pthread_once。

    #include <pthread.h>
    
    pthread_once_t initflag = PTHREAD_ONCE_INIT;
    int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
    //成功返回0,失败返回错误码。

    4).一旦关键被创建,就可以把线程私有数据关联到这个键,通过调用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_getspecific将返回一个空指针,可以使用它来确定是否需要调用pthread_setspecific

    6.取消选项

    有两个线程属性没有包含在pthread_attr_t结构体中,它们是取消状态和取消类型。这些属性影响了线程响应pthread_cancel所呈现的调用行为。

    1).可取消状态属性可以是PTHREAD_CANCEL_ENABLE或PTHREAD_CANCEL_DISABLE。通过调用pthread_setcancelsatate改变它的可取消状态

    #include <pthread.h>
    
    int pthread_setcancelstate(int state, int *oldstate);
    //成功返回0,失败返回错误号。

    pthread_setcancelstate把当前的可取消状态设置为state,把原来的可取消状态放在由oldstate所指的内存单元,这是一个原子操作

    2).可以调用pthread_testcancel来在你的程序里加入的取消点。

    #include <pthread.h>
    
    void pthread_testcancel(void);

    调用pthread_testcancel时,如果有某个和取消请求正处于未决状态,且取消没有置为无效,那么线程将被取消。但是如果取消被置为无效时,那么调用pthread_testcancel没有任何效果。

    3).调用pthread_setcanceltype来改变取消类型

    #include <pthread.h>
    
    int pthread_setcanceltype(int type, int *oldtype);
    //成功返回0,失败返回错误号。

    type参数可以是PTHREAD_CANCEL_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。

    7.线程和信号

    与前面的进程信号类似

    1).pthread_sigmask函数可以阻止Signal的发送:

    #include <siganl.h>
    
    int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
    //成功返回0,失败返回错误号。

    2).调用sigwait函数可以等待signal的产生:

    #include <signal.h>
    
    int sigwait(const sigset_t *restrict set, int *restrict signop);
    //成功返回0,失败返回错误号。

    当signal是pending的情况下,调用sigwait会立刻返回并且把signal从pending list中移走,这样这个signal就不会被调用。为了避免这种行为,可以将pthread_sigmask和sigwait合用,首先用pthread_sigmask在signal产生之前阻止某个signal,然后用sigwait等待这个signal。Sigwait会自动Unblock这个signal,然后在等待结束之后恢复mask。

    3).调用pthread_kill可以给一个线程发送signal:

    #include <signal.h>
    
    int pthread_kill(pthread_t thread, int signo);
    //成功返回0,失败返回错误码。


     

    8.线程和fork

    当线程调用fork的时候,整个进程的地址空间都被copy(严格来说是copy-on-write)到子进程。所有互斥量Mutex / 读写锁Reader-Writer Lock / 信号量Condition Variable的状态都被继承下来。子进程中,只存在一个线程,就是当初调用fork的进程的拷贝。由于不是所有线程都被copy,因此需要将所有的同步对象的状态进行处理。(如果是调用exec函数的话没有这个问题,因为整个地址空间被丢弃了)处理的函数是pthread_atfork:

    #include <pthread.h>
    
    int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
    //成功返回0,失败返回错误号。

    父进程和子进程最后解锁存储在不同内存位置的复制的锁,好像以下的事件序列发生一样:

    1).父进程申请它所有的锁;

    2).子进程申请它所有的锁;

    3).父进程释放它的锁;

    4).子进程释放它的锁。

  • 相关阅读:
    Hadoop 中利用 mapreduce 读写 mysql 数据
    Highcharts AJAX JSON JQuery 实现动态数据交互显示图表 柱形图
    php实现浏览器端大文件分块上传
    csharp实现浏览器端大文件分块上传
    PAT-2019年冬季考试-甲级 7-3 Summit (25分) (邻接矩阵存储,直接暴力)
    PAT-2019年冬季考试-甲级 7-2 Block Reversing (25分) (链表转置)
    PAT-2019年冬季考试-甲级 7-1 Good in C (20分)
    PAT 甲级 树专题小结
    PAT 甲级 1020 Tree Traversals (25分)(后序中序链表建树,求层序)***重点复习
    Python中elasticsearch插入和更新数据的实现方法
  • 原文地址:https://www.cnblogs.com/biyeymyhjob/p/2622725.html
Copyright © 2011-2022 走看看