1、POSIX标准多线程
发送给进程下一组线程的信号将被共享,被其中任意一个线程处理。同一进程创建的线程同生死,如果收到一个kill信号,这一组task_struce都会退出。
2、NPTL模型
内核采用线程组的概念,task_struct增加了tgid,发送信号时能够发送到同一进程下的所有线程,getid会为同一进程下的所有线程返回相同的进程ID。
如上图所示,所有线程虽然都有自己的pid,但是getpid返回值都为一个pid,称为tgid,在TOP命令中显示的就是TGid,如下图所示:
上面一个进程创建了两个线程,打印出来的pid都为6875,在top中显示出来的也是6875.
3、信号量
POSIX中对信号量的操作有下面几个函数
Sem_wait 减一操作,阻塞
Sem_trywait 减一操作,非阻塞模式
Sem_post 加一操作
信号量中经典的生产消费者问题
互斥锁为shard.mutex,拿到该锁才能对仓库进程操作,生产者增加库存,消费者减少库存。
线程中对互斥锁的操作函数如下
pthread_mutex_lock(&mutex)锁定互斥锁
Pthread_mutex_unlock(&mutex)打开互斥锁
可以通过gdb调试两个线程共享互斥锁,通过bt查看每个线程堆栈,再通过l function, p mutex_i判断互斥锁的当前拥有者。
上图中两个线程互相拥有对方的互斥锁。执行时会产生死锁,
通常处理rmw问题,确保读写不同时发生。
4、helgrind
在gcc 4.9之后,可以通过helgrind扫描线程故障,例如对同一变量写冲突不加锁。
这样在gcc 4.9之后不仅加入了内存检测,也加入了线程故障检测。
5、可重入与线程安全
可重入函数满足两个条件:第一是线程安全,第二是函数可软中断,执行完中断处理函数后再回头执行函数,结果仍然正确。
线程安全就是不会出现死锁情况,执行过程中不可能会崩溃。但是不满足可重入条件,如果被异步信号打断后执行结果可能会出错。要使线程安全函数变成可重入函数,需要将线程中全局变量等去掉,让线程只能访问临时变量。
目前linux中存在线程不安全函数,在单线程执行时没有问题,但是多线程执行时需要注意保护。
6、线程栈保护
在线程栈中插入保护页,如果指针落在保护页上,则进程崩溃,保护正常栈,防止越界。
Pthread_getschedualparam
7、优先级反转
优先级反转为高优先级进程被中等优先级进程抢占。
常见的情况是,低优先级先进入了临界区,高优先级到了想用临界区,需要等到低优先级执行完,此时来了中等优先级,中等优先级先执行,然后低优先级执行,还回锁,高优先级最后执行。
可以将低优先级继承高优先级属性,确保不会被中等优先级打断,在低优先级还回锁后再将优先级降低,这样确保高优先级可以立刻执行。
8、多线程编程
I/O原则,大量运算原则,确保CPU和I/O能够同时执行,不要互相等待。
工作组模型,常用于多核同时编译 make –j 4 4个核同时编译
线程池,一开始创建很多线程,将其睡眠,来一个任务唤醒一个线程,执行完后睡眠,减少了创建撤销线程的大量时间开销。
9、IO编程
采用AIO异步读取I/O数据,这样不需要CPU进行干预。实现CPU I/O并行。
10、系统调试
Top命令,按1后能看到每个核的运行情况
在top中需要关注load average,分别对应系统1分钟,5分钟,15分钟负载,正常1分钟负载大于5分钟大于15分钟,如果时间越长负载越高,那证明系统确实性能不行。CPU负载不等于CPU利用率。虚拟化技术能够将CPU和I/O资源利用率提高,因为多个虚拟机能够将任务尽可能乱序,增加并发可能性。
Cpu0和cpu1后面参数差不多表明两个核负载差不多,这是理想情况。
Htop工具,直观显示
Oprofile 可以显示程序落在库和可执行库的时间比例
Perf主推,可以图显示进程执行过程