我是靠谱客的博主 从容发箍,最近开发中收集的这篇文章主要介绍线程控制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、线程限制
1. 获取OS对于线程使用的限制指标:
下述4个指标可以使用sysconf函数获取,不同OS的支持情况也不同,参见P313,表12-1。四个限制值如下:
(1)、PTHREAD_DESTRUCTOR_ITERATIONS:     线程退出时OS试图销毁线程私有数据的最大重试次数。(2)、PTHREAD_KEYS_MAX: 进程可以创建的键的最大数目。
(3)、PTHREAD_STACK_MIN:一个线程的栈可用的最小字节数。
(4)、PTHREAD_THREADS_MAX:进程可以创建的最大线程数。
     尽管OS对于线程存在这四方面的限制,但是具体的限制值并不一定是可以被访问的,也不一定被sysconf函数支持(P314)。在编程时需要知道这四类限制存在,但不代表一定可以获取这四类限制的具体数值。
二、线程的属性
1.使用方法:

   初始化属性变量 à 设置属性值 à 在pthread_create的时候传入属性 --> 销毁属性
2. 属性变量的初始化和销毁:
   初始化属性变量:   pthread_attr_init( attr_ptr )
   销毁属性变量: pthread_attr_destory( attr_ptr )
   pthread_attr_destory可能会失败(如果正确初始化/设置属性,一般不会失败),但如果的确失败了,处理方法如下:
   a). 忽略 --> 可能会内存泄露
   b). 如果一定要清理,需要确保线程被销毁,然后再pthread_attr_destory
   c). 如果pthread_attr_init成功,而pthread_attr_destory失败,则无药可救
3. 线程属性列表,及编辑属性的API(func( *attr, *val1, [*val2] ))

注1:OS是否支持线程栈属性:
编译时,使用 _POSIX_PTHREAD_ATTR_STACKADDR, _POSIX_PTHREAD_ATTR_STACKSIZE
运行时,使用 _SC_THREAD_ATTR_STACKADDR, _SC_THREAD_ATTR_STACKSIZE为参数的sysconf函数确定。
XSI标准的OS,可以保证支持线程栈属性。但是POSIX标准的OS,不一定能保证。
注2:对新旧接口的支持
Linux 2.4.22, Mac OS X10.13 支持新接口
FreeBSD 5.2.1, Solaris 9 不支持新接口
注3:默认值为PAGE_SIZE,如果调用过系统栈设置函数,说明用户希望自己管理内存,系统默认的境界区内存会失效,相当于将警戒区内存大小设置为0
注4:如果用了大量的自动变量或者涉及很深的栈帧,则需要增大线程栈;
如果启动了大量的线程,累计的内存大小超过了可用的虚拟地址空间,则需要减少线程栈。
注5:malloc,mmap($14.9)一段地址空间,使用pthread_attr_setstack,attr参数地址与处理器结构相应的边界对齐
三、线程同步对象的属性:
为互斥量、读写锁、条件变量 这三类同步对象设置属性,属性封装在另外一个struct中,成为属性对象。

1. 互斥量的属性:
(1). 使用方法:

初始化属性对象(mutex_attr_struct)à 设置属性(get/set) à 初始化互斥量对象(mutex struct),同时传入属性对象(mutex_attr_struct) à 使用同步对象(mutexing) à 销毁同步对象(mutex_struct) à 销毁属性对象(mutex_attr_struct)
(2). 初始化/销毁:
pthread_mutexattr_init / pthread_mutexattr_destory
(3). 互斥量的多进程可见(PRIVATE/SHARED)属性
判断OS是否支持这个属性:
编译时的判定:
_POSIX_THREAD_PROCESS_SHARED
运行时的判定:使用_SC_THREAD_PROCESS_SHARED为参数的sysconf函数
用途:
    PTHREAD_PROCESS_PRIVATE(默认):只在单个进程中可见,但线程库可以使用更有效的实现。
    PTHREAD_PROCESS_SHARED:如果将属性为PTHREAD_PROCESS_SHARED的互斥量分配在多个进程的共享内存中,那么这些进程就都可以访问这个互斥量。
属性获取和设置的API:
    pthread_mutexattr_getpshared( *attr, *pshared )
    pthread_mutexattr_setpshared( *attr, pshared ) 
(3). 互斥量的类型属性:
各个类型的行为

2. 读写锁的属性:
只支持 PRIVATE/SHARED属性,决定该读写锁是否可以被多个进程共享
pthread_rwlockattr_init
pthread_rwlockattr_destory
pthread_rwlockattr_getshared
pthread_rwlockattr_setshared

3. 条件变量的属性
只支持 PRIVATE/SHARED属性,决定该条件变量是否可以被多个进程共享
pthread_condattr_init
pthread_condattr_destory
pthread_condattr_getpshared
pthread_condattr_setshared

四、线程安全的函数
1. 线程安全的函数与可重入的函数:
(1). 线程安全函数:指一个函数可以在同一时刻,被多个线程安全地调用。
(2). 可重入的函数:在执行过程中,可以被信号处理程序临时中断,在信号处理程序结束完之后,能够继续正确地执行的函数(P246)
(3). 线程安全函数不一定是可重入的,反之亦然。
(4). 既是线程安全,又可重入的函数,被称为异步—可重入函数。
2. 判断函数是否是线程安全的:
(1). 在POSIX.1 中,不保证线程安全的函数: P324 表12-5
( ctermid,tmpnam,wcrtomb,wcsrtombs 比较特殊,要看传入的参数 )
(2). 用来替代的,线程安全版本的函数(需要OS支持):P325 表12-6
通过 _POSIX_THREAD_SAFE_FUNCTIONS 或者参数为 _SC_THREAD_SAFE_FUNCTIONS的sysconf函数来判断OS是否提供了线程安全版本的函数。

五、线程私有数据
1. 用途:
每个线程有一个独立的副本,互不影响,给基于进程的接口提供了一个适应多线程环境的机会(注:使用线程id作为数组下标,存放线程私有数据的方法是错误的)
2. 使用方法:
pthread_key_create(key,destruct_func):创建一个键
pthread_getspecific(key):获取线程私有数据的地址(还没有set时,返回NULL)
pthread_setspecific(key, val):设置线程私有数据的地址
pthread_key_delete(key,destruct_func):
(1)、pthread_key_create( key, destruct_func ):
初始化key,传入析构函数,“thread_private_data_addr[key]”(存放线程私有数据的内存地址)是NULL。
* 每个线程最多能分配多少个key(OS限制):
12-1: PHTREAD_KEYS_MAX 
* 析构函数何时有效并被执行:
在“thread_private_data_addr[key]”为非空时有效。
在线程调用pthread_exit,执行返回,正常退出时,会被自动调用。
在线程调用了exit,_exit,_Exit,abort或其他非正常退出时,不会被调用。
如果存放线程私有数据的地址是malloc的,可能会因异常退出而导致内存泄露。
* 析构函数的递归调用:
析构函数中可能分配并使用了其他的key,在所有析构函数执行完之后,OS再次检查是否有需要析构的私有数据,直到最大尝试次数(PTHREAD_DESTRUCTOR_ITERATIONS)
* 多个线程创建同一个key:
多个线程创建同一个key时,只需要create一次。为了确保不会多次create,并且不发生竞争条件。pthread_key_create需要用PTHREAD_ONCE_INIT来调用,仅仅由第一个执行pthread_key_create来调用。
(2)、pthread_getspecific:
* 获取线程私有数据的地址。
(3)、pthread_setspecific(key, value):
* 设置线程私有数据的地址。
(4)、pthread_key_delete(key):
* 解除key与线程私有数据之间的绑定(但是不会调用析构函数)

六、线程和信号
1. 线程对于信号的处理行为(屏蔽字,处理方法,递送,屏蔽字继承)

--> 线程的信号屏蔽字(独有):每个线程有自己的信号屏蔽字 
--> 新建的线程会继承现有的信号屏蔽字(P336)
--> 信号处理方法(共享):    所有线程共享信号处理方法 
带来的问题:修改处理函数,设置处理方式(DEFAULT,IGNORE,…)都会影响其他线程
--> 信号递送(不同信号递送方式不同):
硬件/计时器引起的信号à递送到新发信号的线程
其他信号 --> 递送到所有线程
2. 线程处理信号的API:
(1). pthread_sigmask: 用来为线程阻塞信号
(2). sigwait:等待信号发生
--> 如果等待的信号处于未决状态:sigwait将无阻塞地返回
--> 在调用sigwait之前,需要先屏蔽所等待的信号:sigwait将自动解除阻屏蔽,在处理完信号之后,将恢复线程的信号屏蔽。
--> 如果有多个线程等待同一个信号:只有一个线程会被唤醒。
--> 如果信号被设置了捕捉函数,同时又调用了sigwait:时调用了捕捉函数,还是sigwait函数返回,是不确定的,取决于OS。
--> Alarm是进程资源,多个线程不可能互不影响地使用alarm($习题12.6)
(3). pthread_kill:发送信号到指定的线程
--> 可以用pthread_kill(thread, 0 )来检查thread是否还存在(前提是默认处理动作不是终止进程)

七、线程和fork
1. 多线程的process在fork的时候发生了什么?问题是什么?
多线程的process在fork的时候:
为子进程创建了整个地址空间的副本,子进程从父进程继承了所有的(1)互斥量(2)读写锁(3)条件变量。但是子进程中,只有一个线程(调用fork的那个线程)。
问题:
继承了锁,却有可能没有继承占有这些锁的线程。
解决方法:
pthread_atfork( void (*prepare)(void), void (*parent)(void), void (*child)(void) )
用这个函数来注册锁清理函数。prepare在fork之前被调用,parent在fork之后的父进程地址空间被调用,child在fork之后子进程的地址空间被调用。(三个函数都可以传入空指针)。
可以注册多套,parpare的调用顺序与注册顺序相反,parent和child则相同。这样可以由不同的模块来注册需要清除的锁。
由于fork会复制地址空间(同时也复制了锁),因此fork之前加锁一次,fork之后子进程和父进程都需要各自解锁一次。P338
2. 条件变量是否也可以清除?
有些OS不需要清理条件变量。
有些则把锁作为条件变量的一部分(锁被嵌入到条件变量的数据结构中),此时就需要清理,但问题是没有可移植的接口可以用来清理,因此fork之后也就不能使用这些条件变量了(有的操作系统使用了全局锁保护条件变量,这些OS可以在fork的库函数中清理)。
八、多线程对文件的读写:
1. pread, pwrite:
将lseek与read(或write)合并成一个原子操作,解决并发线程对同一个文件读写的问题。

最后

以上就是从容发箍为你收集整理的线程控制的全部内容,希望文章能够帮你解决线程控制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(88)

评论列表共有 0 条评论

立即
投稿
返回
顶部