LESSON1
目标:掌握线程下基本改进
了解线程的优势
进程:一个正在执行的程序 ,它是资源分配的最小单位
进程中的事情需要按照一定的顺序逐个进行
线程:有时又称轻量级进程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,它是进程的一个实体。一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含了一点必不可少的资源。进程只是为了申请资源,真正执行的是线程,例如一个程序中 的主函数main就是一个线程
进程出现了很多弊端,一是由于进程是资源拥有者,创建 撤销与切换存在较大的时空开销y,因此要引进轻量型进程;二是由于多处理机(SMP)出现,可以满足多个运行单位而多个进程并行开销过大
重点难点:
并发 并行,同步 异步
并发:并发是指在同一时刻,只能有一条指令执行,但多个进程指令b被快速轮换执行,使得在宏观上具有多个进程同时执行的效果
看起来同时发生,单核。
并行:并行是指在同一时刻,有多条指令在多个处理器上同时执行
真正的同时发生
同步:彼此有依赖关系的调用不应该同时发生,而同步就是要阻止那些同时发生的事情。
异步:异步的概念和同步相对,任何两个彼此独立的操作是异步的,它表明事情独立发生
多线程的优势
1、在多处理器中开发程序的并行性
2、在等待慢速IO操作时,程序可以执行其他操作,提高并发性
3、模块化的编程,能更清晰的表达程序中独立事件的关系,结构清晰
4 占用较少的系统资源
多线程不一定要多处理器
考核目标:并发并行的区别
LESSON2
**课程目标**:
学会创建一个线程
掌握pthread_create()的参数意义
线程ID获取用pthread_self()
掌握线程的生命周期
重点难点:
pthread_create的参数:
int pthread_create(pthread_t *tidp,const pthread_attr_t attr,(void)(start_rtn)(void),void *arg);
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
返回值 表示成功,返回0;表示出错,返回-1。
线程的生命周期
进程或主线程结束,所有线程都会结束。可以用pthread_exit退出主进程来等待其他进程完成
回收
线程的分离属性:
分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,优势这种线程被称为“僵尸线程”.创建线程时默认是非分离的。
如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未释放的系统资源和进程资源,包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等。
终止被分离的线程会释放所有的系统资源,但是你必须释放由该线程占有的程序资源。例如由malloc火鹤mmap分配的 内存可以在任何时候由任何线程释放,条件变量,互斥量、信号灯可以由任何线程销毁,只要他们被解锁了或者没有线程等待。但是只有互斥量的主人才能解锁它,所以在线程中之前,你需要解锁互斥量。
主线程是在默认堆栈上运行,这个栈k可以增长到足够的长度。而普通线程的堆栈是受限制的,一旦溢出就会产生错误
考核目标 :
如何给新线程传递多个参数——有结构体指针
线程有几种基本状态
作业:编写应用程序,创建一个线程,向该线程传递一个结构体并打印该收到的数据
:两个线程交替打印一个打印奇数一个打印偶数
LESSON3:
课程目标:
1.掌握线程的终止方式
exit是危险的
如果进程中的任意一个线程调用了exit,那么整个进程都会退出
普通的单个线程有以下三种方式退出,这样不会终止进程
(1)从启动例程中返回也就是return,返回值是线程的退出码
(2)线程可以被同一进程中 的其他线程取消
(3)线程调用pthread_exit(voidraval)函数,raval是退出码 是函数的返回值 直接用(void)加一个数就可
2.掌握线程的链接、退出操作
int pthread_join(pthread_t tid,void**rval)
调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit、returen或者被取消
参数tid就是指定线程的id
参数rval是指定线程的返回码。可以在前面先定义好此指针。如果线程被取消,那么rval被置为PTHREAD_CANCELED 其实就是-1
函数调用成功返回0 失败返回错误码
调用pthread_join会使指定的线程处于分离状态,如果指定线程已经处于分离状态,那么调用就会失败。
pthread_detach可以分离一个线程,线程可以自己分离自己
int pthread_detach(pthread_t pid)
成功返回0失败返回错误码
当pthread_join返回以后,其他的线程就不能调用pthread_join链接pid指定的线程了。一个线程被成功的链接了其他线程就无法连接了。join属性的线程,需要同一个进程中的其他线程,获取线程终止的状态,并释放资源.
detach属性的线程,在线程结束后,由系统释放资源.
3.线程的取消
取消函数
int pthread_cancel(pthread_t tid)
取消tid指定的线程,成功返回0.取消只是发送一个请求,并不意味着等待线程结束,发生成功也不意味着tid一点会终止
用于取消一个线程,它通常需要被取消线程的配合。线程在很多时候会查看自己是否有取消请求,如果有就主动退出,这些查看是否有取消的地方称为取消点。
很多地方都包含取消点,包括
篇thread_join()、pthread_testcancel,pthread_cond_waid()、sem_wai()、sigwait()
取消状态
就说线程对取消信号的处理方式,忽略或者响应。线程创建时默认相应取消信号
int pthread_setcancelstate(int state,int*oldstate)(只对本线程生效)
设置本线程对cancel信号的反应,
参数1:PTHREAD_CANCEL_ENABLE响应取消信号和PTHREAD_CANCEL_DISABLE忽略取消信号
canshu2:一般为NULL,不为NULL时则存入旧的的取消状态以便恢复
取消类型
是线程对取消信号的相应方式,立即取消或延时取消。线程创建时默认延时取消
int pthread_setcanceltype(int type,int*oldtype)
设置本线程取消动作的执行时机
参数1:PTHREAD_CANCEL_DEFFERED到下一个取消点取消PTHREAD_CANCEL_ASYNCHRONOUS立即取消,当cancel状态为enable时有效。
参数2:NULL,不为NULL则会保存旧的取消类型
4向线程发送信号
int pthread_kill(pthread_t pthread,int sig)
参数一:被发送信号的进程的ID
参数二:信号
如果参数2为0则是一个保留信号,判断线程是不是还活着。
0:调用成功。
ESRCH:线程不存在。
EINVAL:信号不合法。
5进程对信号的处理
#include<signal.h>
int sigaction(int signum,const struct sigactionact,struct sigactionoldact)
参数1:信号
参数2与参数3:参数结构sigaction定义如下
struct sigaction {
void ( * sa_handler)(int); (信号集处理程序)
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; (信号屏蔽字)
int sa_flags;
void (*sa_restorer)(void);
};
多线程信号屏蔽处理
int pthread_setmask(int how ,const sigset_t set,sigest_toldset)
参数1:SIG_BLOCK:向当前的信号掩码中添加set,其中set表示要阻塞的信号组。
SIG_UNBLOCK:像当前的信号掩码中删除set,其中 set表示要取消阻塞的信号组
SIG_SETMASK:将当前的信号掩码替换为set,其中set表示新的信号掩码
在多线程中,新线程的当前信号掩码会继承创造它的那个线程的信号掩码
一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如sigsegv;另外不能被忽略处理的信号sigkill和sigstop也无法被阻塞
6.线程的清理操作
线程可以安排它退出时的清理 操作,这与进程用atexit函数安排进程退出时需要调用的函数类似。线程可以建立多个清理程序,处理程序记录在栈中,所以处理程序执行顺序与他们注册时的顺序相反
(1)为什么要使用线程清理函数
1.假设遇到取消请求,线程执行到了取消点,却没有来得及做清理动作(如动态申请的内存没有释放,申请的互斥量没有解锁等), 可能会导致错误的产生,比如死锁,甚至是进程崩溃。
2.为了避免这种情况,线程可以设置一个或多个清理函数,线程取消或退出时,会自动执行这些清理函数,以确保资源处于一致的状态。
(2)相关接口
void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数1:注册的函数名
参数2:注册的函数参数
void pthread_cleanup_pop(int execute);
注意:
1.Linux 就是用宏来实现的。这意味着这两个函数必须同时出现,并且属于同一个语法块(同一函数中)。
2. pthread_cleanup_push(clean_func,clean_arg);
…
if(cond)
pthread_cleanup_pop(0);
//在日常编码中很容易犯上面这种错误。因为 pthread_cleanup_push 和 phtread_cleanup_pop 的实现中包
//含了 { 和 } ,所以将 pop 放入 if{} 的代码块中,会导致括号匹配错乱,最终会引发编译错误。
3.可以注册多个清理函数
pthread_cleanup_push(clean_func_1,clean_arg_1)
pthread_cleanup_push(clean_func_2,clean_arg_2)
pthread_cleanup_pop(execute_2);
pthread_cleanup_pop(execute_1);
3.从 push 和 pop 的名字可以看出,这是栈的风格,后入先出,就是后注册的清理函数会先执行
其中 pthread_cleanup_pop 的用处是,删除注册的清理函数。如果参数是非 0 值,那么执行一次,再删除清理函数。否则的话,就直接删除清理函数。
(3)何时触发清理函数 (在Ubuntu16上实测) :
1.pthread_cancel:
1.线程取消前执行了pthread_cleanup_pop(0), 就不会执行清理函数
2.否则执行清理函数
2.pthread_exit()
1.如果在pthread_exit前执行了pthread_cleanup_pop(0), 清理函数不会执行
2.如果在pthread_exit前执行了pthread_cleanup_pop(1), 清理函数会执行
3.pthread_exit在pthread_cleanup_pop函数前被执行, 清理函数一定会执行
3.return()
1.如果在return前执行了pthread_cleanup_pop(0), 清理函数不会执行
2.如果在return前执行了pthread_cleanup_pop(1), 清理函数会执行
3.return在pthread_cleanup_pop函数前被执行, 清理函数一定不会执行
————————————————
版权声明:本文为优快云博主「周厚平」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_38813056/article/details/85857582
重点难点:
线程的链接、清理操作
考核目标:
线程的清理操作是如何进行的
LESSON4:
***课程目标,***掌握线程同步的方法
restrict,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
为了让线程访问数据不产生冲突,这就需要对变量加锁。使得同一时刻只有一个线程可以访问变量。互斥量本质就是锁,访问共享资源前对互斥量加锁,访问完成后解锁
pthread_mutex_t 类型,其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数看待。
pthread_mutex_t mutex:变量mutex只有两种取值0、1;
互斥量用pthread_mutex_t类型数据表示,在使用之前需要对互斥量初始化
头文件:pthreadtypes。h
初始化一个互斥锁(互斥量);\rightarrow 初值可看做1
1.pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
返回值:若成功,返回0,否则,返回错误编号
参数1:要初始化的互斥量,调用时应传&mutex
参数2:互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).
静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。pthread_mutex_init(&mutex, NULL);
2.销毁一个互斥量(互斥锁)
pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:若成功,返回0,否则,返回错误编号
3.功能:加锁。可理解为将mutex–(或-1)
#include<pthread.h>
pthread_mutex_lock(pthread_mutex_t *mutex);
返回值:若成功,返回0,否则,返回错误编号
分析:
没有被上锁,当前线程会将这把锁锁上
被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞。
-
函数原型:
功能:解锁。可理解为将mtex++(或+1)
#include<pthread.h>
pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:若成功,返回0,否则,返回错误编号
注意:
同时将阻塞在该锁上的所有线程全部唤醒
- 函数原型:
功能:尝试加锁, 失败返回, 不阻塞
#include<pthread.h>
pthread_mutex_trylock(pthread_mutex_t *mutex);
返回值:若成功,返回0,否则,返回错误编号
分析:
没有锁上:当前线程会给这把锁加锁
如果锁上了:不会阻塞,返回
————————————————
版权声明:本文为优快云博主「月雲之霄」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/isunbin/article/details/83415873
读写锁
头文件thread。h
读写锁与互斥量类似,不过读写锁有更高的并行性。互斥量要么加锁要么不加锁,而且同一时刻只允许一个线程对其加锁。对于一个变量的读取,完全可以让多个线程同时进行操作。
phtread_relock_t rwlock
读写锁有三种状态,读模式下加锁,写模式下加锁,不加锁。一次只有一个线程可以占有写模式下 的读写锁,但是多个线程可以同时占有读模式的读写锁。
读写锁在写加锁状态时,所有试图对这个锁加锁的线程都会阻塞。读写锁在读加锁状态时,所有试图以读模式对其加锁的线程都会获得访问权,但是如果以写模式对其加锁,它必须阻塞直到所有的线程释放锁。当读写锁以读模式加锁时,如果有线程试图以写模式对其加锁,那么读写锁会阻塞随后的读模式请求读写锁在使用前必须初始化,在释放其底层内存前必须销毁
1.函数原型 功能初始化读写锁
int pthread_rwlock_init( pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
参数1:定义的读写锁的地址
参数2:用NULL表示默认属性
成功返回0否则返回错误编码
2.函数原型 功能销毁一个 读写锁
int pthread_rwlock_destroy(pthread_rwlock *rwlock);
参数:读写锁地址
返回值成功返回0否则返回错误编码
3.函数原型 功能 尝试以读方式锁住读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
参数:读写锁地址
返回值调用成功返回0,失败返回错误码。
4.函数原型 功能以写方式锁住读写锁
int pthread_rwlock_ wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
参数:读写锁地址
返回值调用成功返回0,失败返回错误码。
加try的不会阻塞程序,
5.函数原型
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数:读写锁指针
返回值成功返回0
重点难点:
互斥量、读写锁、条件变量的区别
合理同步,避免死锁
练习:使用多线程把文件1.c拷贝到11.c 22.c 33.c
条件变量
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待
————————————————
版权声明:本文为优快云博主「清风徐来_starthere」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_39736982/article/details/82380689
pthread_cond_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:
pthread_cond_t cond; 变量cond只有两种取值1、0。
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
1.函数原型 功能:初始化一个条件变量
pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr))函数
参数2:一般有NULL 默认属性
2.函数原型 功能:阻塞等待一个条件变量
pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)函数
阻塞等待条件变量cond(参1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
3.函数原型 功能:限时等待一个条件变量
pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime)函数
参3: 参看man sem_timedwait函数,查看struct timespec结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参
4.函数原型 功能:唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t *cond)函数
5.函数原型 功能:唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond)函数
6.函数原型 功能:销毁一个条件变量
pthread_cond_destroy(pthread_cond_t *cond)函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
LSEEON5: 线程的高级属性
一次性初始化
有些事需要且只能执行一次(比如互斥量初始化)。通常当初始化应用程序时,可以比较容易地将其放在main函数中。但当你写一个库函数时,就不能在main里面初始化了,你可以用静态初始化,但使用一次性初始化(pthread_once_t )会比较容易些。
首先要定义一个pthread_once_t变量,这个变量要用宏PTHREAD_ONCE_INIT初始化。然后创建一个与控制变量相关的初始化函数
void init_routine
{
//初始化互斥量
//初始化读写锁
}
接下来就可以在任何时刻调用pthread_once函数
int pthread_once(pthnread_once_t*once_control,void(*init_routine)(void));
参数1:定义的一次性初始化参数
参数2:定义的初始化函数
保证canshu2的函数只执行一次,即使此函数调用出现在多个线程中,但是也只执行一次,究竟在哪个线程中执行是不确定的
pthread_once函数其实是用条件变量来定义的 ,pthread_once_t变量初始化值为0.当为0时所有调用都可以调用此函数,如果有线程正在调用此函数,则变量值变为1,此时所有进程在调用此函数时就会进入阻塞等待状态等待。调用完成一次之后变量会变为2.此时所有进程调用此函数都会立即返回。
线程的属性
线程的属性用 pthread_attr_t类型的结构表示,在创建线程的时候可以不用传入NULL,而是传入一个pthread_attr_t 结构,由用户自己来配置线程的属性
pthread_attr_t 类型对应用程序是不透明的,也就是说应用程序不需要了解有关属性对象内部结构的任何细节,因而可以增加程序的可移植性
线程属性
名称 描述
detachstate 线程的分离状态
guardsize 线程栈末尾的警戒区大小(字节数)
stacksize 线程栈的最低地址
stacksize 线程栈的大小(字节数)
并不是所有的系统都支持线程的这些属性,因此你需要检查当前系统是否支持你设置的属性
当然还有一些属性不包含在pthread_attr_t 结构中,例如:线程的可取消状态、取消类型、并发度
pthread_attr_t结构在使用之前需要初始化,使用完之后需要销毁
线程属性初始化
int pthread_attr_initt(pthread_attr_tattr)
线程属性销毁
int pthread_attr_destroy(pthread_attr_tattr)
如果在调用pthread_attr_init初始化属性的时候分配了内存空间,那么pthread_attr_destroy将释放内存空间。除此之外,pthread_attr_destory还会有无效的值初始化pthread_attr_t对象,因此如果该属性对象被误用,会导致创建线程失败。
线程的分离属性
如果在创建线程的时候就知道不需要了解线程的终止状态,那么可以修改pthread_attr_t 结构体的detach state属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数来设置线程的分离状态属性。线程的分离属性有两种和发展
PTHREAD_CREATE_DETACHED分离的
PTHREAD_CREATE_JOINABLE分离的,可连接
int pthread_attr_setdetachstate(pthread_attr_tattr,int detachstate)
int pthread_attr_getdetachstate(pthread_attr_tattr,int*detach state)
使用第二个get函数可以获得线程的分离状态属性
设置线程分离属性的步骤‘
定义线程属性变量pthread_attr_t attr
初始化attr,prhread_attr_init(&attr)
设置线程为分离或非分离pthread_attr_setdetachstate(&attr,detachstate)
创建线程pthread_create(&tid,&attr,thread_fun,NULL)
线程的同步数据
线程的私有数据
如何在线程中使用fork
线程的栈属性
对于进程来说。虚拟地址空间的大小是固定的,进程中只有一个栈,因此它的大小通常不是问题。但对线程来说,同样的虚拟地址被所有的进程共享。如果应用程序使用了太多的线程,致使线程栈累计超过可用的虚拟地址空间,这个时候就需要减少线程默认的栈大小,另外,如果线程分配了大量的自动变量或者线程的栈帧太深,这个时候需要的栈比默认的大
如果用完了虚拟地址空间,可以使用malloc或者mmap为其他栈分配空间,并修改栈的位置
修改栈属性
int pthread_attr_setstack(pthread_attr_tattr,voidstackaddr,size_t stacksize)
获取栈属性
int pthread_attr_getstack(pthread_attr_tattr,viod**stackaddr,size_tstacksize)
参数关于stackaddr是栈的内存单元最低地址,参数stacksize是栈的大小。你要注意stackaddr并不一定是栈的开始,对于一些处理器,栈的地址是从高往低
栈尾警戒区guardsize控制着线程栈末尾以后用以避免栈溢出的扩展内存的大小,这个属性默认是PAGESIZE字节。你可以把它设为0,这样就不会提供警戒缓冲区。同样的如果修改了stackaddr 系统会认为你要自己管理栈,境界缓冲区会无效
设置栈大小int pthread_attr_setstack(pthread_attr_tattr,size_t guardsize)
获取栈大小int pthread_attr_getstack(pthread_attr_tattr,size_t guardsize)
有的系统不一定支持线程的栈属性
因此要检查
用_POSIX_THREAD_ATTR-STACKADDR和_POSIX_THREAD_ATTR-STACKSIZE
来检查系统是否支持栈属性,宏定义在(/usr/include/bits/posix_opt。h)
进程同步属性
互斥量属性
共享属性和类型属性。互斥量的属性用pthread_mutexattr_t类型的数据表示,当然在使用之前必须进程初始化,使用完成之后需要进行销毁
互斥量属性初始化
int pthread_mutexattr_init(pthread_mutexattr_tattr)
互斥量属性销毁
int pthread_mutexattr_destroy(pthread_mutexattr_tattr)
进程共享属性有两种值:
PTHREAD_PROCESS_PRIVATE,这个锁默认值,同一进程中的多个线程访问同一个同步对象
PTHREAD_PROCESS_SHARED。这个属性可以使互斥量在多个进程中进行同步,如果互斥量在多进程的共享内存区域,那么具有这个属性的互斥可以同步多进程
查看互斥进程共享属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_trestrict attr,intrestrict pshared)
设置互斥进程共享属性
int pthread_mutexattr_setpshared(const pthread_mutexattr_t*restrict attr,int pshared)
进程共享属性需要检测系统是否支持,可以检测宏_POSIX_THREAD_PROCESS_SHARED
类型属性
互斥量属性 没有解锁时再次加锁 不占用时解锁 已解锁时解锁
PTHREAD_MUTEX_NOMARL 死锁 未定义 未定义
PTHREAD_MUTEX_ERRORCHEK 返回错误 返回错误 返回错误
PTHREAD_MUTEX_RECURSIVE 允许 返回错误 返回错误
PTHREAD_MUTEX_DEFAULT 未定义 未定义 未定义
查看互斥量类型
int pthread_mutexattr_gettype(const pthread_mutexattr_trestrict attr,intrestrict type)
设置互斥量类型
int pthread_mutexattr_settype(const pthread_mutexattr_t*restrict attr,int type)
线程的私有数据
应用程序设计中有必要提供一种变量,使得多个函数多个线程都可以访问这个变量(看起来是个全局变量),但是线程对这个变量的访问都不会彼此产生影响。这种数据就说线程的四哟数据,尽管名字相同,但是每个线程访问的都是数据的副本。
在使用私有数据之前,你首先要创建一个私有数据相关的键,要来获取对私有数据的访问权限。这恶键的类型是pthread_key_t。
int pthread_key_create(pthread_key_t*key,void(*destructor)(void* ))
//参数1是数据地址,参数2是销毁这个数据的析构函数里面的参数是key的地址一般填NULL
创建的键放在key指向的内存单元,destructor是与键相关的析构函数。当线程调用pthread_exit或者使用return返回。析构函数就会被调用。当析构函数调用的时候,它只有一个参数,这个参数是与key关联的那个数据地址因此你可以在析构函数中将这个数据销毁。
键使用完之后也可以销毁,当键销毁之后,与它管理的数据并没有销毁。
int pthread_ley_delete(pthread_key_t key)
有了键之后,你就可以将私有数据和键关联起来,这样就可以通过键来找到数据。所有的线程都可以访问这个键,但他们可以为键关联不同的数据。
int pthread_setspecific(pthread_key_t key,const void*value)
参数一是创立的键,参数二是要关联的数据的地址
void*pthread_getspecific(pthread_key_t key)
获取私有数据的地址,如果没有数据与key关联,那么返回空。
线程与fork
当线程调用fork函数时,就为子进程创建了整个进程地址空间的副本,紫禁城通过继承整个地址空间的副本,也会将父进程的互斥量,读写锁,条件变量的状态继承过来。也就是说,如果父进程中互斥量是锁着的,那么子进程中互斥量也是锁着的(尽管子进程自己还没有来得及lock’),这是非常不安全的,因为不是子进程自己锁住的,它无法解锁。
子进程内部只有一个线程,由父进程调用fork函数的线程副本构成。如果调用fork的线程将互斥量锁住,那么子进程会拷贝一个pthread_mutex_lock副本,这样子进程就有机会去解锁了。或者互斥量根本就没被加锁,这样也是可以的,但是你不能确保永远是这样的情况。
pthread_atfork函数给你创造了这样的条件,它会注册三个函数
int pthread_atfork(void(*prepare)(void),void(*parent)(void),void(*child)(void));
prepare是在fork调用之前会被调用的,parent在fork返回父进程之前调用,child在fork返回子进程之前调用。如果在prepare中加锁所有的互斥量,在parent和child中解锁所有的互斥量,那么在fork返回之后,互斥量的状态就说未 加锁。
可以有多个pthread_atfork函数,这是也就会有多个处理程序(prepare parent child)。多个prepare的执行顺序与注册顺序相反,而parent和chid的执行顺序与注册顺序相同