APUE学习笔记6——线程和线程同步
1 概念
线程是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。
线程包含了表示进程执行环境必须的信息,包括线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽字和errno变量以及线程私有数据。
采用多线程的好处:
- 通过为每种事件类型的处理分配不同的线程,能够简化处理异步事件的代码。
- 多个进程必须采用复杂的方式才能实现内存和文件描述符的共享,而线程可以访问相同的存储空间和文件描述符。
- 通过分解成多个线程可以增加程序的吞吐量。
- 交互的程序依赖于多线程实现响应时间的改善。
1.1 线程标识
每个线程有个线程ID,线程ID只在它所属的进程里有效,用pthread_t数据类型来表示。
#include <pthread.h>
/*进程ID不能直接比较*/
int pthread_equal(pthread_t tid1, pthread_t pid2);
/*若相等返回非0值,不等返回0*/
pthread_t pthread_self(void);
/*返回自己的线程ID*/
2 线程创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);
/*成功返回0,否则返回错误编号*/
当pthread_create成功返回时,由tidp所指向的内存单元被设置为新创建线程的线程ID。
attr参数用于定制线程的不同属性。
新创建的线程从start_rtn处开始运行,该函数只有一个无类型指针参数arg。
注意:新线程和主线程的执行先后顺序是不确定的。
3 线程终止
如果进程中的任意一个线程调用了exit、_Exit或_exit,那么整个进程就会终止。当信号的默认动作是终止进程,把该信号发送到线程也会终止整个进程。
单个线程可以以下三种方式退出:
- 线程从启动例程中返回。
- 线程被同一进程中其他线程取消。
- 线程调用pthread_exit
#include <pthread.h> void pthread_exit(void *rval_ptr);//rvta_ptr为线程终止返回值 /*访问其他线程终止的返回值,等待*/
int pthread_join(pthread_t tid, void **rval_ptr); /*请求取消同一进程中的其他线程,不等待*/
int pthread_cancel(pthread_t tid);
线程可以安排它退出时需要调用的函数
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);//清理函数为rtn,参数为arg
void pthread_cleanup_pop(int execute);//如果execute为0,则清理函数将不被调用
线程执行以下动作时调用清理函数
- 调用pthread_exit时
- 相应pthread_cancel请求时
- 用非0参数调用pthread_cleanup_pop时
现在可以看出线程函数和进程函数之间的相似之处:
| 进程原语 | 线程原语 | 描述 |
| fork | pthread_create | 创建新的控制流 |
| exit | pthread_exit | 从现有控制流中退出 |
| waitpid | pthread_join | 从控制流中得到退出状态 |
| atexit | pthread_cancel_push | 注册在退出控制流时调用的函数 |
| getpid | pthread_self | 获取控制流ID |
| abort | pthread_cancel | 请求控制流的非正常退出 |
默认情况下,线程的终止状态会保留到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层资源可以在线程终止时立刻被收回。
/*调用pthread_detach函数使线程处于分离状态*/
#include <pthread.h>
int pthread_detach(pthread_t tid);//成功返回0,失败返回错误编号
当线程被分离时,不能使用pthread_join函数等待它的终止状态。
4 线程同步
当多个线程共享相同的内存单元时,就容易出现每个线程看到的数据不一致的问题。当多个线程都要修改某个变量时,就需要对它们进行同步,以确保它们在任意的时刻都不会访问到无效的数值。
为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问这个变量。
4.1 互斥量
互斥量(mutex)本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问后释放互斥量上的锁。对互斥量加锁之后,如果其他线程试图访问将被阻塞直到当前进程释放该互斥锁。
互斥变量用pthread_mutex_t数据类型来表示。使用之前必须先进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER。如果动态地分配互斥量(例如malloc),那么在释放内存前需要调用pthread_mutex_destory。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restric mutex, const pthread_mutexattr_t *restrict attr);
/*attr为属性参数,设置attr为NULL则默认参数*/ int pthread_mutex_destory(pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);//加锁成功返回0,失败返回EBUSY int pthread_mutex_unlock(pthread_mutex_t *mutex); /*若成功返回0,否则返回错误编号*/
如果互斥量已经上锁,线程调用pthread_mutex_lock将阻塞直到互斥量被解锁。如果不希望被阻塞,则调用pthread_mutex_trylock
4.2 避免死锁
如果线程试图对同一个互斥量加锁两次,就会进入死锁状态。两个线程试图互相锁住对方拥有的互斥量,也会陷入死锁状态。
可以小心控制互斥量加锁的顺序来防止死锁发生。也可以先释放占有的锁,过一段时间再试。
4.3 读写锁
读写锁与互斥量相似,不过读写锁允许更高的并行性。读写锁有三个状态:读加锁、写加锁和不加锁状态。一次只允许一个线程占用写锁,但是多个线程可以同时占用读锁。
| 目前读写锁的状态 | 其他线程试图的动作 | 结果 |
| 写加锁状态 | 对这个锁加锁(读或写) | 阻塞直到锁被释放 |
| 读加锁状态 | 对这个锁进行读模式的加锁 | 可以获得访问权 |
| 读加锁状态 | 对这个锁进行写模式的加锁 | 阻塞直到锁被释放,并且会阻塞随后的读模式加锁,避免读模式被长期占用 |
读写锁非常适合读大于写的状况。读写锁又叫共享-独占锁,读模式是共享的,写模式是独占的。
和互斥锁一样,读写锁使用之前必须初始化,在释放它的内存资源之前必须销毁它。
#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //attr可以为NULL int pthread_rwlock_destory(pthread_rwlock_t *restrict rwlock); int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock); //可能对获取锁的数量限制,注意检查其返回值 int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock); //有条件的读写锁 int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock); /*上述函数返回值:若成功返回0,否则返回错误编号*/
4.4 条件变量
条件变量和互斥量一起用的时候,允许线程以无竞争的方式等待特定条件的发生。
条件变量本身由互斥量保护的,所以线程在改变条件之前必须锁住互斥量。
条件变量使用之前必须初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,在释放它的内存资源之前必须销毁它。
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //attr可以为NULL
int pthread_cond_destory(pthread_cond_t *restrict cond);
int pthread_cond_wait(pthread_rwlock_t *restrict rwlock, const pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_rwlock_t *restrict rwlock, const pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
/*上述函数返回值是:成功返回0,否则返回错误编号*/
struct timespec{
time_t tv_sec; //秒
long tv_nsec; //毫秒
//时间值是一个绝对数,需要将当前时间加上要等待的时间
}
pthread_cond_wait等待条件变为真,调用者把锁住的互斥量传递给pthread_cond_wait的进行保护,函数把调用线程放到等待条件的线程列表上,然后对互斥量进行解锁,这两个操作是原子操作。pthread_cond_wait返回时,互斥量再次被锁住。有两个函数用于通知线程条件已经满足。
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); //唤醒等待该条件的某个线程 int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒等待该条件的所有线程 /*成功返回0,否则返回错误编号*/
/*一定要改变条件状态之后再给线程发送信号*/
(完)
APUE学习笔记6——线程和线程同步的更多相关文章
- Android(java)学习笔记267:Android线程池形态
1. 线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...
- Android(java)学习笔记211:Android线程池形态
1. 线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...
- JavaSE学习笔记(13)---线程池、Lambda表达式
JavaSE学习笔记(13)---线程池.Lambda表达式 1.等待唤醒机制 线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同. 比如:线程A用来生成包子的,线程B用 ...
- JavaSE学习笔记(12)---线程
JavaSE学习笔记(12)---线程 多线程 并发与并行 并发:指两个或多个事件在同一个时间段内发生. 并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在 ...
- 转:学习笔记: Delphi之线程类TThread
学习笔记: Delphi之线程类TThread - 5207 - 博客园http://www.cnblogs.com/5207/p/4426074.html 新的公司接手的第一份工作就是一个多线程计算 ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- APUE学习笔记——10.9 信号发送函数kill、 raise、alarm、pause
转载注明出处:Windeal学习笔记 kil和raise kill()用来向进程或进程组发送信号 raise()用来向自身进程发送信号. #include <signal.h> int k ...
- APUE学习笔记3_文件IO
APUE学习笔记3_文件IO Unix中的文件IO函数主要包括以下几个:open().read().write().lseek().close()等.这类I/O函数也被称为不带缓冲的I/O,标准I/O ...
- APUE 学习笔记(八) 线程同步
1. 进程的所有信息对该进程内的所有线程都是共享的 包括 可执行的程序文本.程序全局内存.堆内存以及文件描述符 线程包含了表示进程内执行环境必需的信息,包括线程ID.寄存器值.栈.调度优先级和策略.信 ...
随机推荐
- input输入值限制
限制输入框只能输入数字并且保留两位小数 <input type= "text" onkeyup="var p2 = parseFloat(value).toFixe ...
- ios高效开发二--ARC跟block那点事
block是可以捕捉上下文的特殊代码块. block可以访问定义在block外的变量,当在block中使用时,它就会为其在作用域内的每个标量变量创建一个副本. 如果通过self拥有一个block,然后 ...
- django模板使用
概述 模板由两部分组成,HTML代码,逻辑控制代码,作用:快速生成HTML页面,优点:模板的设计实现了业务逻辑与现实内容的分离 定义模板 挖坑与继承 模板继承可以减少页面的内容的重复定义,实现页面的重 ...
- 探索Python的多态是怎么实现的
多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为. 对于其他如C++的语言,多态是通过在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来 ...
- SpringBoot-CommandLineRunner实现预操作
前提:在使用SpringBoot构建项目时,我们通常需要做一些预先操作(类似开机自启动).而SpringBoot正好提供了一个简单的方式来实现–CommandLineRunner. CommandLi ...
- Makefile错误总结
自己在做嵌入式驱动时,编写makefile文件是犯的错及解决办法 问题1:makefile 3 missing separator.stop: 问题2:Nothing to be done for ' ...
- flash透明 处于最低
怎样在html中让flash透明 前提是FLASH里没有用其它形状或图形来作为背景.方法主要是在网页中的Flash加入一个参数,让网页设定Flash文件背景透明,Flash文件本身做不到. 关键: & ...
- BA--步进电机工作原理
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元步进电机件.在非超载的情况下,电机的转速.停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,当步进驱动器接收到一个脉冲信号,它就驱动 ...
- POJ 2110
终于过了,SHIT,二分+DFS即可,二分区间,根据数字是否在区间内,变成迷宫题了.枚举第一个格子,二分上界,即可. #include <iostream> #include <cs ...
- Linux Unix shell 编程指南学习笔记(第三部分)
第十三章 登陆环境 登陆系统时.输入username和password后.假设验证通过.则进入登录环境. 登录过程 文件/etc/passwd $HOME.profile 定制$HOME.profi ...