1.介绍
本以为暂停线程是一条语句的事,结果发现事情并没有那么简单,而且我相信很多人都用错了,就是因为踩了这么一个坑,所以准备发这么一个帖子,让后面的人少踩坑。
2.应用场景
有三个线程,分别为A、B和C,把A线程比作是大脑,B和C线程分别是扫地和洗碗,首先处于空闲状态,大脑让我去扫地,这时A线程中会开启B线程,第一次开启线程可以调用【rt_thread_startup】这个函数,没啥毛病,当老婆大人让我去洗碗,大脑瞬间做出反应去洗碗,这时A线程会关闭B线程然后打开C线程,那么A线程关闭B线程是调用【rt_thread_suspend】这个函数吗?看着没啥问题哦,但是里面存在很大的问题!这里先不说,继续往下走,当洗完碗还得继续扫地,让A线程恢复B线程需要调用【rt_thread_resume】这个函数。
上面这个应用场景就实现了,A线程暂停和恢复B线程,看着是没什么问题,我们先来实际操作一下,看看有什么问题!
3.场景再现
硬件:STM32F407VET6
RTThread版本:3.1.3Nano
首先按照上面的应用场景走一遍,设计三个线程,分别为A、B和C,然后开始运行,具体代码如下:
  1. #include <rtthread.h>
  2. ALIGN(RT_ALIGN_SIZE)
  3. static rt_uint8_t a_thread_stack[ 512 ];
  4. ALIGN(RT_ALIGN_SIZE)
  5. static rt_uint8_t b_thread_stack[ 512 ];
  6. ALIGN(RT_ALIGN_SIZE)
  7. static rt_uint8_t c_thread_stack[ 512 ];
  8. static struct rt_thread a_thread;
  9. static struct rt_thread b_thread;
  10. static struct rt_thread c_thread;
  11. static void a_thread_entry(void *parameter)
  12. {
  13.     unsigned int count = 0;
  14.     while (1)
  15.     {
  16.         count++;
  17.         if(count == 10)
  18.         {
  19.             rt_kprintf("b start!\n");
  20.             rt_thread_startup(&b_thread);   //开始扫地
  21.         }
  22.         else if(count == 30)
  23.         {
  24.             rt_kprintf("b pause!\n");
  25.             rt_thread_suspend(&b_thread);   //停止扫地
  26.             rt_kprintf("c start!\n");
  27.             rt_thread_startup(&c_thread);   //开始洗碗
  28.         }
  29.         else if(count == 50)
  30.         {
  31.             rt_kprintf("c pause!\n");
  32.             rt_thread_suspend(&c_thread);   //停止洗碗
  33.             rt_kprintf("b start!\n");
  34.             rt_thread_resume(&b_thread);    //开始扫地
  35.         }
  36.         rt_thread_delay(100);
  37.     }
  38. }
  39. static void b_thread_entry(void *parameter)
  40. {
  41.     unsigned int count = 0;
  42.     while (1)
  43.     {
  44.         rt_kprintf("b thread run!\n");
  45.         rt_thread_delay(300);
  46.     }
  47. }
  48. static void c_thread_entry(void *parameter)
  49. {
  50.     unsigned int count = 0;
  51.     while (1)
  52.     {
  53.         rt_kprintf("c thread run!\n");
  54.         rt_thread_delay(100);
  55.     }
  56. }
  57. int pause_thread_init(void)
  58. {
  59.     rt_err_t result;
  60.     /* init led thread */
  61.     result = rt_thread_init(&a_thread,
  62.                             "a_thread",
  63.                             a_thread_entry,
  64.                             RT_NULL,
  65.                             (rt_uint8_t *)&a_thread_stack[0],
  66.                             sizeof(a_thread_stack),
  67.                             5,
  68.                             5);
  69.                            
  70.     if (result == RT_EOK)
  71.     {
  72.         rt_thread_startup(&a_thread);
  73.     }
  74.    
  75.     /* init led thread */
  76.     result = rt_thread_init(&b_thread,
  77.                             "b_thread",
  78.                             b_thread_entry,
  79.                             RT_NULL,
  80.                             (rt_uint8_t *)&b_thread_stack[0],
  81.                             sizeof(b_thread_stack),
  82.                             6,
  83.                             5);
  84.                            
  85.    
  86.     /* init led thread */
  87.     result = rt_thread_init(&c_thread,
  88.                             "c_thread",
  89.                             c_thread_entry,
  90.                             RT_NULL,
  91.                             (rt_uint8_t *)&c_thread_stack[0],
  92.                             sizeof(c_thread_stack),
  93.                             7,
  94.                             5);
  95.                            
  96.     return 0;
  97. }
  98. /* 导出到 msh 命令列表中 */
  99. MSH_CMD_EXPORT(pause_thread_init, pause thread);
来看看实际的效果,如下图1所示,可以发现调用【rt_thread_suspend】函数并没有真正停止线程B或线程C。
1.png

图1

这是为什么呢?我们上RTThread官网看一下,观看图2可以发现【rt_thread_suspend】的函数说明中,特意的说明了这个函数不能通过A线程挂起B线程。
2.png

图2

同时我也上网查了一下,大家都有这个疑问,然后我们翻开源码看一下,在这个函数中有这么一个判断,会判断需要挂起线程的状态,如果线程不等于就绪态那么就不会进入挂起,而sleep,delay等函数都会导致线程的挂起,那么其他线程想要挂起这个线程肯定会掉到这个if里面,从而挂起不了这个线程。
3.png

图3

4.解决方案
可以使用删除线程和创建线程的方式来停止线程的运行,但是这个对线程有一定的要求,比如线程没有运行时长的变量,如果有的话,线程删除了这个变量的值也会被复位,要么这个变量设置为全局变量,但是这样就不太合适,代码实现如下所示:
  1. #include <rtthread.h>
  2. ALIGN(RT_ALIGN_SIZE)
  3. static rt_uint8_t a_thread_stack[ 512 ];
  4. ALIGN(RT_ALIGN_SIZE)
  5. static rt_uint8_t b_thread_stack[ 512 ];
  6. ALIGN(RT_ALIGN_SIZE)
  7. static rt_uint8_t c_thread_stack[ 512 ];
  8. static struct rt_thread a_thread;
  9. static struct rt_thread b_thread;
  10. static struct rt_thread c_thread;
  11. static void b_thread_entry(void *parameter);
  12. static void c_thread_entry(void *parameter);
  13. static void a_thread_entry(void *parameter)
  14. {
  15.     unsigned int count = 0;
  16.     while (1)
  17.     {
  18.         count++;
  19.         if(count == 10)
  20.         {
  21.             rt_kprintf("b start!\n");
  22.             /* init led thread */
  23.             rt_thread_init(&b_thread,
  24.                             "b_thread",
  25.                             b_thread_entry,
  26.                             RT_NULL,
  27.                             (rt_uint8_t *)&b_thread_stack[0],
  28.                             sizeof(b_thread_stack),
  29.                             6,
  30.                             5);
  31.             rt_thread_startup(&b_thread);   //开始扫地
  32.         }
  33.         else if(count == 30)
  34.         {
  35.             rt_kprintf("b pause!\n");
  36.             rt_thread_detach(&b_thread);    //停止扫地
  37.             rt_kprintf("c start!\n");
  38.             rt_thread_init(&c_thread,
  39.                             "c_thread",
  40.                             c_thread_entry,
  41.                             RT_NULL,
  42.                             (rt_uint8_t *)&c_thread_stack[0],
  43.                             sizeof(c_thread_stack),
  44.                             7,
  45.                             5);
  46.             rt_thread_startup(&c_thread);   //开始洗碗
  47.         }
  48.         else if(count == 50)
  49.         {
  50.             rt_kprintf("c pause!\n");
  51.             rt_thread_detach(&c_thread);    //停止洗碗
  52.             rt_kprintf("b start!\n");
  53.             rt_thread_init(&b_thread,
  54.                             "b_thread",
  55.                             b_thread_entry,
  56.                             RT_NULL,
  57.                             (rt_uint8_t *)&b_thread_stack[0],
  58.                             sizeof(b_thread_stack),
  59.                             6,
  60.                             5);
  61.             rt_thread_startup(&b_thread);   //开始扫地
  62.         }
  63.         rt_thread_delay(100);
  64.     }
  65. }
  66. static void b_thread_entry(void *parameter)
  67. {
  68.     unsigned int count = 0;
  69.     while (1)
  70.     {
  71.         rt_kprintf("b thread run!\n");
  72.         rt_thread_delay(300);
  73.     }
  74. }
  75. static void c_thread_entry(void *parameter)
  76. {
  77.     unsigned int count = 0;
  78.     while (1)
  79.     {
  80.         rt_kprintf("c thread run!\n");
  81.         rt_thread_delay(100);
  82.     }
  83. }
  84. int pause_thread_init(void)
  85. {
  86.     rt_err_t result;
  87.     /* init led thread */
  88.     result = rt_thread_init(&a_thread,
  89.                             "a_thread",
  90.                             a_thread_entry,
  91.                             RT_NULL,
  92.                             (rt_uint8_t *)&a_thread_stack[0],
  93.                             sizeof(a_thread_stack),
  94.                             5,
  95.                             5);
  96.                            
  97.     if (result == RT_EOK)
  98.     {
  99.         rt_thread_startup(&a_thread);
  100.     }
  101.                            
  102.     return 0;
  103. }
  104. /* 导出到 msh 命令列表中 */
  105. MSH_CMD_EXPORT(pause_thread_init, pause thread);
查看下图2,可以看到这个方式可以实现线程停止的功能,但是这种方式非常有局限性,而且开销比较大,因为需要不停的初始化和脱离线程,并且对任务中的变量有一定的要求,非常容易让代码产生BUG,一般不采纳。
4.png

图4

这里我推荐使用信号量的方法来暂停线程,首先定义一个暂停信号量,然后在需要暂停的线程中去不停的监测这个信号量,当接收到信号量时,自己主动挂起线程并让出CPU,这样就可以实现暂停线程,而且还能够知道线程暂停在哪。代码实现如下:
  1. #include <rtthread.h>
  2. ALIGN(RT_ALIGN_SIZE)
  3. static rt_uint8_t a_thread_stack[ 512 ];
  4. ALIGN(RT_ALIGN_SIZE)
  5. static rt_uint8_t b_thread_stack[ 512 ];
  6. ALIGN(RT_ALIGN_SIZE)
  7. static rt_uint8_t c_thread_stack[ 512 ];
  8. static struct rt_thread a_thread;
  9. static struct rt_thread b_thread;
  10. static struct rt_thread c_thread;
  11. static rt_sem_t b_pause_sem = RT_NULL;
  12. static rt_sem_t c_pause_sem = RT_NULL;
  13. static void a_thread_entry(void *parameter)
  14. {
  15.     unsigned int count = 0;
  16.     while (1)
  17.     {
  18.         count++;
  19.         if(count == 10)
  20.         {
  21.             rt_kprintf("b start!\n");
  22.             rt_thread_startup(&b_thread);   //开始扫地
  23.         }
  24.         else if(count == 30)
  25.         {
  26.             rt_kprintf("b pause!\n");
  27.             rt_sem_release(b_pause_sem);//rt_thread_suspend(&b_thread);   //停止扫地
  28.             rt_kprintf("c start!\n");
  29.             rt_thread_startup(&c_thread);   //开始洗碗
  30.         }
  31.         else if(count == 50)
  32.         {
  33.             rt_kprintf("c pause!\n");
  34.             rt_sem_release(c_pause_sem);//rt_thread_suspend(&c_thread);   //停止洗碗
  35.             rt_kprintf("b start!\n");
  36.             rt_thread_resume(&b_thread);    //开始扫地
  37.         }
  38.         rt_thread_delay(100);
  39.     }
  40. }
  41. static void b_thread_entry(void *parameter)
  42. {
  43.     unsigned int count = 0;
  44.     while (1)
  45.     {
  46.         rt_kprintf("b thread run!\n");
  47.         rt_thread_delay(300);
  48.         if(rt_sem_take(b_pause_sem, 0) == RT_EOK)
  49.         {
  50.             rt_thread_suspend(&b_thread);   //停止扫地
  51.             rt_schedule();
  52.         }
  53.     }
  54. }
  55. static void c_thread_entry(void *parameter)
  56. {
  57.     unsigned int count = 0;
  58.     while (1)
  59.     {
  60.         rt_kprintf("c thread run!\n");
  61.         rt_thread_delay(100);
  62.         if(rt_sem_take(c_pause_sem, 0) == RT_EOK)
  63.         {
  64.             rt_thread_suspend(&c_thread);   //停止扫地
  65.             rt_schedule();
  66.         }
  67.     }
  68. }
  69. int pause_thread_init(void)
  70. {
  71.     rt_err_t result;
  72.    
  73.     b_pause_sem = rt_sem_create("b_pause", 0, RT_IPC_FLAG_PRIO);
  74.    
  75.     c_pause_sem = rt_sem_create("c_pause", 0, RT_IPC_FLAG_PRIO);
  76.     /* init led thread */
  77.     result = rt_thread_init(&a_thread,
  78.                             "a_thread",
  79.                             a_thread_entry,
  80.                             RT_NULL,
  81.                             (rt_uint8_t *)&a_thread_stack[0],
  82.                             sizeof(a_thread_stack),
  83.                             5,
  84.                             5);
  85.                            
  86.     if (result == RT_EOK)
  87.     {
  88.         rt_thread_startup(&a_thread);
  89.     }
  90.    
  91.     /* init led thread */
  92.     result = rt_thread_init(&b_thread,
  93.                             "b_thread",
  94.                             b_thread_entry,
  95.                             RT_NULL,
  96.                             (rt_uint8_t *)&b_thread_stack[0],
  97.                             sizeof(b_thread_stack),
  98.                             6,
  99.                             5);
  100.                            
  101.    
  102.     /* init led thread */
  103.     result = rt_thread_init(&c_thread,
  104.                             "c_thread",
  105.                             c_thread_entry,
  106.                             RT_NULL,
  107.                             (rt_uint8_t *)&c_thread_stack[0],
  108.                             sizeof(c_thread_stack),
  109.                             7,
  110.                             5);
  111.                            
  112.     return 0;
  113. }
  114. /* 导出到 msh 命令列表中 */
  115. MSH_CMD_EXPORT(pause_thread_init, pause thread);
运行结果如图5所示,可以正常暂停,当然这个办法也有一些问题,挂起线程会有延时,只要这个延时不影响程序的整体运行,这个方法还是非常不错的!
5.png

图5

5.总结
希望RTThread能出一些更好暂停线程的方法,上述的方法只能作为应急使用,并不是最优的解决方案,A线程暂停B线程,这个场景是经常会使用到的,所以希望可以优化这个功能!