尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

系统api-线程控制

系统api-线程控制 线程控制线程限制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线程属性(1). 每个对象与它自己类型的属性对象进行关联线程与线程属性关联…。属性对象对应用程序不透明有相应的函数来管理这些属性对象。(2). 有一个初始化函数把属性设置为默认值。(3). 有一个销毁属性对象的函数。如初始化函数分配了与属性对象关联的资源销毁函数负责释放这些资源。(4). 每个属性有一个从属性对象中获取属性值的函数。成功返回0。失败返回错误编号。(5). 每个属性有一个设置属性值的函数。属性值按值传递。detachstate线程的分离状态属性intpthread_attr_init(pthread_attr_t*attr);intpthread_attr_destroy(pthread_attr_t*attr);intpthread_attr_getdetachstate(constpthread_attr_t*restrict attr,int*detachstate);intpthread_attr_setdetachstate(pthread_attr_t*attr,int*detachstate);如果对现有的某个线程的终止状态不感兴趣的话可使用pthread_detach让操作系统在线程退出时收回它所占用的资源。如在创建线程时不需要了解线程终止状态可修改pthread_attr_t的detachstate属性让线程一开始就处于分离状态。可用pthread_attr_setdetachstate把线程属性detachstate设为PTHREAD_CREATE_DETACHED以分离启动线程。或PTHREAD_CREATE_JOINABLE正常启动线程应用程序可获取线程终止状态。guardsize线程栈末尾的警戒缓存区大小字节数stackaddr线程栈的最低地址stacksize线程栈的最小长度字节数对遵循POSIX标准的系统不一定要支持线程栈属性。对遵循XSI选项的系统需要支持。可在编译阶段用_POSIX_THREAD_ATTR_STACKADDR和_POSIX_THREAD_ATTR_STACKSIZE来检查系统是否支持每个线程栈属性。也可在运行阶段把_SC_THREAD_ATTR_STACKADDR和_SC_THREAD_ATTR_STACKSIZE传给sysconf。可用pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理。intpthread_attr_getstack(constpthread_attr_t*restrict attr,void**restrict stackaddr,size_t*restrict stacksize);intpthread_attr_setstack(pthread_attr_t*attr,void*stackaddr,size_t stacksize);intpthread_attr_getstacksize(constpthread_attr_t*restrict attr,size_t*restrict stacksize);intpthread_attr_setstacksize(pthread_attr_t*attr,size_t stacksize);线程属性guardsize控制着线程栈末尾后用以避免栈溢出的扩展内存的大小。属性默认值由具体实现定义。常用值是系统页大小。如果修改了线程属性stackaddr系统就认为我们将自己管理栈使警戒缓冲区机制无效。等同于把guardsize设为0。intpthread_attr_getguardsize(constpthread_attr_t*restrict attr,size_t*restrict guardsize);intpthread_attr_setguardsize(pthread_attr_t*attr,size_t guardsize);如果线程的栈指针溢出到警戒区域应用程序可能通过信号接收出错信息。同步属性(1). 互斥量属性互斥量属性用pthread_mutexattr_t结构表示。intpthread_mutexattr_init(pthread_mutexattr_t*attr);intpthread_mutexattr_destroy(pthread_mutexattr_t*attr);值得注意的3个属性是进程共享属性健壮属性类型属性。PTHREAD_PROCESS_SHARED从多进程共享的内存块中分配的互斥量可用于共享进程间的同步。intpthread_mutexattr_getpshared(constpthread_mutexattr_t*restrict attr,int*restrict pshared);intpthread_mutexattr_setpshared(pthread_mutexattr_t*attr,intpshared);互斥量健壮属性与在多个进程间共享的互斥量有关。持有互斥量进程终止时需要解决互斥量状态恢复问题。intpthread_mutexattr_getrobust(constpthread_mutexattr_t*restrict attr,int*restrict robust);intpthread_mutexattr_setrobust(pthread_mutexattr_t*attr,introbust);健壮属性默认值为PTHREAD_MUTEX_STALLED意味着持有互斥量的进程终止时不需要采取特别的动作。这时使用互斥量后行为未定义。等待该互斥量的应用会被拖住。另一个取值是PTHREAD_MUTEX_ROBUST将导致后续其他等待此锁线程阻塞。使用健壮的互斥量需检查pthread_mutex_lock的3个返回值不需要恢复的成功需要恢复的成功失败。如应用状态无法恢复线程对互斥量解锁后互斥量永久不可用。为避免可调用pthread_mutex_consistent指明与该互斥量相关的状态在互斥量解锁前是一致的。intpthread_mutex_consistent(pthread_mutex_t*mutex);如线程没有先调用pthread_mutex_consistent就对互斥量解锁则其他试图获取该互斥量线程得到错误码ENOTRECOVERABLE。类型互斥量属性控制着互斥量的锁定特性。POSIX.1定义了4中类型。PTHREAD_MUTEX_NORMAL不做特殊的错误检查或死锁检测。PTHREAD_MUTEX_ERRORCHECK提供错误检查PTHREAD_MUTEX_RECURSIVE允许同一线程在互斥量解锁前对该互斥量多次加锁。递归互斥量维护锁的计数。在解锁次数和加锁次数不相同下不会释放锁。PTHREAD_MUTEX_DEFAULT由系统将其映射为上述三者的一种互斥量类型没解锁时重新加锁不占有时解锁在已解锁时解锁PTHREAD_MUTEX_NORMAL死锁未定义未定义PTHREAD_MUTEX_ERRORCHECK返回错误返回错误返回错误PTHREAD_MUTEX_RECURSIVE允许返回错误返回错误PTHREAD_MUTEX_DEFAULT未定义未定义未定义intpthread_mutexattr_gettype(constpthread_mutexattr_t*restrict attr,int*restrict type);intpthread_mutexattr_settype(pthread_mutexattr_t*attr,inttype);(2). 读写锁属性用pthread_rwlockattr_init初始化pthread_rwlockattr_t结构。用pthread_rwlockattr_destroy销毁。intpthread_rwlockattr_init(pthread_rwlockattr_t*attr);intpthread_rwlockattr_destroy(pthread_rwlockattr_t*attr);intpthread_rwlockattr_getpshared(constpthread_rwlockattr_t*restrict attr,int*restrict pshared);intpthread_rwlockattr_setpshared(pthread_rwlockattr_t*attr,intpshared);(3). 条件变量属性进程共享属性时钟属性intpthread_condattr_init(pthread_condattr_t*attr);intpthread_condattr_destroy(pthread_condattr_t*attr);intpthread_condattr_getpshared(constpthread_condattr_t*restrict attr,int*restrict pshared);intpthread_condattr_setpshared(pthread_condattr_t*attr,intpshared);intpthread_condattr_getclock(constpthread_condattr_t*restrict attr,clockid_t*restrict clock_id);intpthread_condattr_setclock(pthread_condattr_t*attr,clockid_t clock_id);重入多个控制线程在相同的时间有可能调用相同的函数。如果一个函数在相同的时间点可被多个线程安全地调用称该函数是线程安全的。支持线程安全函数的系统会在unist.h中定义符号_POSIX_THREAD_SAFE_FUNCTIONS。应用也可在sysconf中传入_SC_THREAD_SAFE_FUNCTIONS。如一个函数对多个线程来说是可重入的称其是线程安全的。如果函数对异步信号处理程序的是安全的就说函数是异步信号安全的。以线程安全方式管理FILE对象。可用flockfileftrylockfile获取给定FILE对象关联的锁。此锁是递归的。intftrylockfile(FILE*fp);voidflockfile(FILE*fp);voidfunlockfile(FILE*fp);intgetchar_unlocked(void);intgetc_unlocked(FILE*fp);// 成功返回c。出错返回EOF。intputchar_unlocked(intc);intputc_unlocked(intc,FILE*fp);线程特定数据在分配线程特定数据前需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。intpthread_key_create(pthread_key_t*keyp,void(*destructor)(void*));创建的键存储在keyp指向的内存单元中可被进程的所有线程使用。但每个线程把这个键与不同线程特定数据地址关联。创建新键时每个线程的数据地址为空值。线程退出时如数据地址已被置为非空值则析构函数会被调用唯一参数为数据地址。线程调pthread_exit或线程返回正常退出析构函数被调用。线程取消时在最后的清理处理程序返回后析构函数被调用。如线程调了exit, _exit, _Exit或abort或其他非正常退出不调用析构。线程退出时线程特定数据的析构函数按系统中定义的顺序调用。所有析构函数完成后检查是否还有非空的线程特定数据值与键关联如有再次析构。可通过pthread_key_delete取消键和线程特定数据值间的关系。intpthread_key_delete(pthread_key_t key);pthread_once_t initflagPTHREAD_ONCE_INIT;intpthread_once(pthread_once_t*initflag,// 需指向一个全局或静态变量初始化为PTHREAD_ONCE_INITvoid(*initfn)(void));void*pthread_getspecific(pthread_key_t key);intpthread_setspecific(pthread_key_t key,constvoid*value);在多线程环境中有些事仅需要执行一次。通常当初始化应用程序时可以比较容易地将其放在main函数中。但当你写一个库时就不能在main里面初始化了你可以用静态初始化但使用一次初始化pthread_once会比较容易些。intpthread_once(pthread_once_t*once_control,void(*init_routine)(void))功能本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。在多线程编程环境下尽管pthread_once()调用会出现在多个线程中init_routine()函数仅执行一次究竟在哪个线程中执行是不定的是由内核调度来决定。// to do取消选项可取消状态可取消类型。可取消状态属性可以是PTHREAD_CANCEL_ENABLE也可是PTHREAD_CANCEL_DISABLE。intpthread_setcancelstate(intstate,int*oldstate);pthread_cancel不等待线程终止。线程在取消请求发出后还是继续运行直到到达某个取消点。线程启动时默认可取消状态为PTHREAD_CANCEL_ENABLE。线程在到达取消点检查是否有取消请求并处理。void pthread_testcancel(void);intpthread_setcanceltype(inttype,int*oldtype);线程和信号每个线程都有自己的信号屏蔽字。但信号的处理所有线程共享。进程中的信号被递送到单个线程。有些信号被发送到任意一个线程。sigprocmask的行为在多线程的进程中没定义。此时线程用pthread_sigmask。intpthread_sigmask(inthow,constsigset_t*restrict set,sigset_t*restrict oset);intsigwait(constsigset_t*restrict set,// 等待的信号集int*restrict signop);// 信号数量intpthread_kill(pthread_t thread,intsigno);闹钟是进程资源。线程和fork线程调用fork时子进程通过继承整个地址空间的副本。从父进程继承了每个互斥量读写锁条件变量。如果父进程包含一个以上线程子进程fork后如果不是马上调exec就需要清理锁的状态。在子进程内部只存在一个线程由父进程中调用fork的线程副本构成。父进程中的线程如占有锁子进程将同样占有这些锁。但子进程不包含占有锁的线程的副本。故子进程不知道占有了哪些锁释放哪些锁。在多线程的进程中为避免不一致状态POSIX.1声明fork返回和子进程调用其中一个exec间子进程只能调用异步信号安全的函数。要清除锁状态可以用pthread_atfork建立forkintpthread_atfork(void(*prepare)(void),void(*parent)(void),void(*child)(void));最多可安装3个帮助清理锁的函数。prepare在由父进程在fork创建子进程前调用用于获取父进程定义的所有锁。parent在fork创建子进程后返回前在父进程上下文调用用于对prepare获取的所有锁进行解锁。child在fork返回前在子进程上下文调用。用于对prepare获取的所有锁进行解锁。好像出现了下列事件序列父进程获取所有的锁子进程获取所有的锁父进程释放它的锁子进程释放它的锁。可多次调用pthread_atfork来设置多套fork处理程序。使用了多个fork处理程序时parentchild处理程序按注册顺序调用。prepare按注册时相反顺序调用。如模块A调模块B的函数且每个模块有自己一套锁。如锁的层次是A在B之前模块B需在模块A之前设置它的fork处理程序。父进程调用fork时会执行以下步骤假设子进程在父进程之前运行。a. 调模块A的prepareb. 调模块B的preparec. 创建子进程d. 调模块B的childe. 调模块A的childf.fork返回到子进程g. 调模块B的parenth. 调模块A的parenti.fork返回父进程但现实中这很难做到你可能不知道所有第三方库创建了哪些锁C 全局对象的内部锁你无权访问忘记注册某个锁就会留下隐患forkexec正确做法fork后父进程持有的锁在子进程也有但子进程只拥有执行fork那个执行流对应的单个线程。fork后执行exec会提供一份干净的虚拟内存但fork和exec之间要避免执行任何需要获取锁的操作。确实需要在forkexec之间执行一些操作时推荐主进程启动初期只有主线程通过fork创建一个中间进程主进程有启动新进程需求时通知中间进程中间进程执行forkexec。线程和I/Opread和pwrite可以使设置文件位置和读取或写入整合为原子操作适合多线程。线程本地变量编程实践1.POSIX TSDpthread_key_t动态适合运行时才知道需要多少份线程私有数据或库函数需要透明地给每个线程分配私有状态如 errno 的历史实现、strerror_r 的缓冲区等。#define_GNU_SOURCE#includepthread.h#includestdio.h#includestdlib.hstaticpthread_key_t tls_key;staticpthread_once_t key_oncePTHREAD_ONCE_INIT;// 析构函数线程退出时如果 value ! NULL自动调用staticvoiddestructor(void*ptr){printf([Thread %lu] Destructor called, freeing %p\n,pthread_self(),ptr);free(ptr);}// 确保全局只创建一次 key类似单例staticvoidcreate_key(void){pthread_key_create(tls_key,destructor);}// 获取线程私有的缓冲区懒初始化staticchar*get_thread_buffer(void){pthread_once(key_once,create_key);char*bufpthread_getspecific(tls_key);if(bufNULL){bufmalloc(1024);pthread_setspecific(tls_key,buf);}returnbuf;}void*worker(void*arg){char*bufget_thread_buffer();snprintf(buf,1024,Hello from thread %lu,pthread_self());printf(%s\n,buf);// 线程退出时destructor 会自动释放 bufreturnNULL;}intmain(){pthread_t t1,t2;pthread_create(t1,NULL,worker,NULL);pthread_create(t2,NULL,worker,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_key_delete(tls_key);// 主线程清理 key 本身return0;}2.编译器 TLS__thread / thread_local静态适合编译期就知道每个线程需要一份的变量如线程 ID 缓存、随机数生成器状态、线程本地计数器等。// GCC/Clang 扩展__threadintthread_id;// C11 标准推荐_Thread_localintthread_id;// C11 标准推荐thread_localintthread_id;实例#includepthread.h#includestdio.h// 每个线程独立拥有一份 counter初始值为 0_Thread_localintcounter0;void*worker(void*arg){intid*(int*)arg;thread_idid;// 每个线程修改自己的副本for(inti0;i5;i){counter;printf([Thread %d] counter %d\n,id,counter);}returnNULL;}intmain(){pthread_t t[2];intids[2]{1,2};for(inti0;i2;i){pthread_create(t[i],NULL,worker,ids[i]);}for(inti0;i2;i){pthread_join(t[i],NULL);}return0;}3.比较特性pthread_key_t__thread/thread_local分配时机运行时pthread_key_create编译期声明数据类型void *需手动类型转换任意类型类型安全性能较慢需哈希/数组查找极快通常通过 FS/GS 段寄存器偏移直接访问析构支持有destructor回调Cthread_local自动调用析构C 的_Thread_local不支持析构适用场景库函数、插件、动态需求全局线程私有状态、计数器、缓存跨平台POSIX 标准C11/C11 标准但__thread是 GCC 扩展
返回列表