发现问题
公司项目测试的时候,发现运行一段时间后会出现cpu百分之百的情况。
想着可能是哪里出现了死循环,于是打算用gdb跟一下,结果gdb居然无法attach到进程。。。。。。
定位问题
查了查去,原来有一个优先级为RT的实时线程出现了死循环,并且由于配置了CPU的亲和属性,使得进程只运行在第一个核上,此时gdb就无法attach了
使用taskset现场修改进程的cpu亲和属性后,发现这个占cpu百分百的实时线程并没有出现一般的死循环,而是每次都在pthread_rwlock_wrlock这个函数中,
而更诡异的是,只要修改了cpu亲和属性,就没有“死循环了”。。。。。。
实验
于是写了一段实验代码
1 #define _GNU_SOURCE2 #include \"stdio.h\"3 #include \"stdlib.h\"4 #include \"unistd.h\"5 #include \"pthread.h\"6 #include <sched.h>78 pthread_rwlock_t rwlock;910 void* task1(void *arg)11 {12 pthread_setname_np(pthread_self(), \"task1\");1314 while(1)15 {16 printf(\"\\r\\n task1 lock \\r\\n\");17 pthread_rwlock_wrlock(&rwlock);1819 printf(\"\\r\\n task1 unlock \\r\\n\");20 pthread_rwlock_unlock(&rwlock);2122 usleep(10);23 }24 }2526 void* task2(void *arg)27 {28 struct sched_param sparam;2930 pthread_setname_np(pthread_self(), \"task2\");3132 /* 设置为最高优先级的实时任务 */33 sparam.sched_priority = sched_get_priority_max(SCHED_RR);34 pthread_setschedparam(pthread_self(), SCHED_RR, &sparam);3536 while(1)37 {38 printf(\"\\r\\n task2 lock \\r\\n\");39 pthread_rwlock_wrlock(&rwlock);4041 printf(\"\\r\\n task2 unlock \\r\\n\");42 pthread_rwlock_unlock(&rwlock);4344 usleep(10);45 }46 }4748 int main(int argc, char *argv[])49 {50 pthread_t t1, t2, t3;51 cpu_set_t cpuset;5253 /* 设置cpu亲和属性,将进程绑定在第一个核上 */54 CPU_ZERO(&cpuset);55 CPU_SET(0, &cpuset);56 sched_setaffinity(0, sizeof(cpuset), &cpuset);5758 pthread_rwlock_init(&rwlock, NULL);5960 pthread_create(&t2, NULL, task1, NULL);61 sleep(3);62 pthread_create(&t3, NULL, task2, NULL);6364 while (1)65 sleep(10);6667 return 0;68 }
运行结果,如下图
真的出现了CPU百分百的情况!!!
分析原因
1. 读写锁的“拿锁”和“放锁”操作并不是一个完整的原子操作,而是有可能操作到一半被调度出去;
2. 此次实验结果显示,task1(非实时)在做unlock操作时,已经修改了一部分读写锁的属性,此时task2(实时)lock时,发现不需要再阻塞了,只需要自旋等待(死循环)task1将unlock操作做完;然而由于task2是实时任务,整个进程又只绑定到了第一个核上,task1无法得到调度,造成了task2的死循环。