进程与线程的区别:
进程:拥有PCB, 有独立的地址空间
线程:light weight process轻量级的进程,本质仍是进程
线程:拥有PCB,没有独立地址空间
线程:CPU最小的执行和调度单位
进程:最小的分配资源单位,多个线程共享进程的资源
是否共享地址空间,独居(进程),合租(线程)
线程的优缺点:
可以在一个进程内实现并发
开销少,创建线程比创建进程要快
数据通信、数据共享方便,同时也增加了开发的难度
学习任务:
线程的创建和终止
线程创建、等待线程退出、查看线程
线程非正常终止
1. 如果主线程退出,全部线程将强行终止
2. 在子线程调用exit(),将终止整个进程
3. 终止程序的信号,将导致整体进程终止.(如果某个线程coredump,但影响整个进程)
在多进程程序中,子进程core dump不影响其他进程
在多线程程序中,子线程core dump,整个进程受影响。
终止线程的三种方法
1. 子线程主函数中,直接返回,返回值是线程退出码
2. 子线程可以被同一进程中的其他线程调用pthread_cancel()取消
3. 子线程调用pthread_exit()方法
return 与pthread_exit的区别就是,如果在子线程的子函数数中return返回到子线程中
如果在子函数中pthread_exit的话,则子线程退出。
线程参数的传递(奇巧淫技)
创建的多个线程并不保证哪个线程先运行
不能用全局变量代替线程函数的参数
数据类型的强制转换
1.如何传递整形参数
2.如何传递地址参数(要传多个参数的时候只能传地址了)
3.线程退出状态(pthread_join第二个参数可以得到线程退出的状态或返回值)
线程资源的回收,让多线程程序有计划的退出
线程的分离
用线程清理函数释放资源
回顾:进程资源的回收
1.子进程退出时,向父进程发送SIGCHLD信号
2.父进程调用wait()函数等待子进程退出,忽略SIGCHLD信号
3.在信号处理函数中释放资源
线程资源回收:
1.线程非分离状态joinable(默认状态)
2.线程分离状态(unjoinable)
3.非分离状态的线程终止时,不会释放线程的全部资源
如果不关心线程退出状态,可以把线程的属性设置为分离,线程结束后,由系统回收资源
1.调用pthread_detach()函数分离线程
2.创建线程前,调用pthread_attr_setdetachstate()设置线程的属性
用线程清理函数,释放资源
线程终止的时候,可以调用清理函数释放资源
清理函数入栈:pthread_cleanup_push()
清理函数出栈:pthread_cleanup_pop()
这两个函数必须成对书写在同一语句块中
线程终止(return, pthread_exit(),pthread_cancel()),
语句块结束的时候,不一定是代码最后一行return,清理函数将被执行。
线程的取消
线程在运行的过程中,可以调用pthread_cancel()取消它
线程被取消后,返回状态是PTHREAD_CANCELED,即-1;
设置线程的取消状态pthread_setcancelstate();
设置线程的取消方式pthread_setcanceltype(); 可以是延时取消(默认),也可以是立即取消。
设置线程的取消点pthread_testcancel();
线程和信号,与多进程的信号大有不同
在多线程程序中,外部向进程发送信号不会中断系统调用
在多线程程序中,信号的处理是所有线程共享的,在子线程中忽略信号,则整个进程中的所有线程都忽略了信号
进程中的信号可以送达单个线程,会中断系统调用
如果某个线程因为信号而终止,整个进程将终止
扩展知识:
比signal()更强大的sigaction()函数
进程信号屏蔽sigprocmask(), 线程pthread_sigmask();
其他信号处理函数sigwait(), sigwaitinfo(), sigtimedwait()
线程安全,一切麻烦从共享资源开始
多个线程访问共享资源(全局和静态)的时候会冲突
三个概念:原子性、可见性和顺序性
原子性:
一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)
CPU执行指令:读取指令、读取内存、执行指令、写回内存
i++ 1)从内存中读取i的值; 2)把i+1; 3)把结果写回内存
可见性:
当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到
cpu有调整缓存。每个线程读取共享变量时,会将变量从内存加载到cpu的缓存中,修改该变量后,cpu会立即更
新缓存,但不一定会立即将它写回内存。此时其它线程访问该变量,从内存中读到的是旧数据,而非第一个线程
更新后的数据
顺序性:
程序执行的顺序按照代码的先后顺序执行
cpu为了提高程序整体的执行效率,可能会对代码进行优化,按照更高效的顺序执行代码
cpu虽然并不保证完全按照代码顺序执行,但它保证程序最终的执行结果与代码顺序执行的结果一致。
如何解决线程安全问题
volatile关键字(解决不了多线程修改同一变量的问题)
保证了变量的内存可见性,cpu每次从内存中读取,不缓存
禁止代码重排序。
因为不是原子的
原子操作
本质是总线锁
三条汇编指令:xadd、cmpxchg或xchg
硬件级别的锁
线程同步
线程同步,各种锁的使用
互斥锁
只有加锁和解锁操作,确保同一时间只有一个线程访问共享资源
访问共享资源之前加锁,访问完成后释放锁
如果某线程持有锁,其它的线程形成等待队列
属性有:PTHREAD_MUTEX_TIMED_NP缺省值,普通锁,保证资源分配的公平性
PTHREAD_MUTEX_RECURSIVE_NP嵌套锁,允许同一个线程对同一个锁成功获得多次
PTHREAD_MUTEX_ADAPTIVE_NP适应锁,解锁后,请求锁的线程重新竞争
自旋锁
互斥锁在等待锁的时候,线程会休眠不消耗CPU,而自旋锁则有一个循环不断地检查锁是否可以使用,消耗CPU
两者没有好坏之分,只是场景不一样。自旋锁适用于等待很短的使用场景,而互斥锁适用于可能等待比较长的场景
所以自旋锁没有提供超时的函数,因为使用此锁是基于等待很短的使用场景,否则用互斥锁
读写锁
读写锁允许更高的并发性
三种状态:读模式加锁(读锁),写模式加锁(写锁),不加锁
特点:
只要没有线程持有写锁,任意线程都可以成功申请读锁
只有在不加锁状态时,才能成功申请写锁
注意事项:
读定你起码适合于对读的次数远大于写的情况
linux系统优先考虑读锁,这种实现方式有可能导致写入线程饿死的情况
pthread_rwlock_t mutex //声明锁
int pthread_rwlock_init() //初始化锁
int pthread_rwlock_destroy();
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_IINITIALIZER
int pthread_rwlock_rdlock() //申请读锁
int pthread_rwlock_tryrdlock() //尝试申请读锁,不阻塞
int pthread_rwlock_timedrdlock() //申请读锁,再超时机制
条件变量
与互斥锁一起使用
实现生产消费者模型
实现通知的功能
信号量
一个整数计数器,其数值用于表示空闲临界资源的数量
申请资源时,信号量减少,表示可用资源数减少
释放资源时,信号量增加,表示可用资源数增加
生产消费者模型
1.条件变量+互斥锁实现生产消费者模型
pthread_cond_wait(&cond, &mutex)
把互斥锁解锁-->阻塞,等待条件(被唤醒)-->条件被触发+给互斥锁加锁
2.信号量实现生产消费者模型
生产消费者模型,代码很精妙
开发多线程的网络服务端程序
ps -Lf 进程编号