线程的调度,都是建立在中断的基础上的,当我们关闭中断,系统将不能进行调度了,还有我们可以禁止调度器调度来保护临界资源。除了这些我们还用线程间通信的方式保护线程间的同步。
IPC机制(Inter-Process Communication)----意思是进程间通信。RT-Thread中IPC对象有:信号量、互斥锁、事件、消息队列、邮箱。
1---线程抢占导致临界区问题
两个线程共同占用一个全局变量,假设线程thread1和thread2优先级分别是7和4,但是程序执行时,thread2会先运行,但是线程thread2中有延时(且非常短),这是thread1得以运行,但是thread1线程并没有运行完毕,这时候thread2的延时到了且优先级高于thread1,thread2线程继续执行,但是这时候它就改变了全局变量的值,当thread1再次执行的时候,已经是更新后的值了。最后的输出结果就会跟预期的结果完全不同了,为了解决这种问题,我们引入了IPC机制。
2---信号量基本使用
信号量像一把钥匙,任务要运行下去,需先拿到这把钥匙。
申请信号量的任务是再说:“把钥匙给我,如果谁在正在用,我只好等!”
详细看RT-Thread内核分析:
http://blog.csdn.net/flydream0/article/details/8592532
信号量有两种操作:take(获取)和Release(释放),当一个线程调用take操作时,他要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时,Release(释放)实际上是在信号量上执行加操作,take(获取)实际上是在信号量上执行减操作。
RT-Thread中信号量有静态和动态之分(同静态线程、动态线程),和信号量有关的操作如下:
初始化——rt_sem_init()(对应静态信号量);
建立——rt_sem_create()(对应动态信号量);
获取——rt_sem_take();
释放——rt_sem_release();
脱离——rt_sem_detach();(对应静态信号量);
删除——rt_sem_delete();(对应动态信号量);
3---用信号量实现任务之间的调度
1.创建一个信号量,且初始值设为0,如下:
result= rt_sem_init(&sem, "sem", 0, RT_IPC_FLAG_FIFO);
f (result != RT_EOK)
{
rt_kprintf("error, init semfailed!\n");
return 0;
}
2.有线程申请得到信号量,代码如下:(由于初始值为0,是无效的(信号量大于0有效),所以该线程只能等待。)
staticvoid rt_thread_entry2(void* parameter)
{
while (1)
{
rt_sem_take(&sem,RT_WAITING_FOREVER);
if (key & KEY_PIN)
{
rt_kprintf("some keyshas been pressed : %x\n", key);
}
}
}
3.线程thread1释放信号量
staticvoid rt_thread_entry1(void* parameter)
{
int temp;
while (1)
{
key = GPIO_ReadInputData(KEY_PORT);
if (key & KEY_PIN)
{
temp = key;
rt_thread_delay(RT_TICK_PER_SECOND / 50);
key =GPIO_ReadInputData(KEY_PORT);
if (key == temp)
rt_sem_release(&sem);
}
rt_thread_delay(RT_TICK_PER_SECOND/10);
}
}
release(释放)信号量,这里信号量的值就会加1,变成有效的了,当此线程执行到delay函数时,系统就会执行thread2线程,此时信号量也是有效的,所以它就会获取信号量,使信号量的值减1。从而打印信息。
4---使用信号量解决生产者消费者问题
生产者消费者问题是一个著名的线程同步问题,
该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费。为了使生产者和消费者能并发执行,在两者之间设置一个具有多个位置的缓冲区,生产者将它生产的产品放入缓冲区中,消费者可以从缓冲区中取走产品进行消费。生产者和消费者之间必须保持同步,即当缓冲区为空时,消费者需要被阻塞(即挂起)直到生产者者生产出产品并放入缓冲区;当缓冲区满时,生产者则需要被阻塞直到消费者冲缓冲区取走产品使得缓冲区有至少一个空位。
注意:
从缓冲区取出产品和向缓冲区投放产品的过程为临界区,必须是互斥进行的,可以使用互斥量(mutex)、二值信号量或调度器上锁实现临界区互斥。
当缓冲区不满时,生产者才可以向缓冲区中投放产品;当缓冲区不空时,消费者才可以从缓冲区中取出产品消费,因此这意味着生产者和消费者各自获取一个信号量。
1.初始化三个信号量
rt_err_tresult;
/*初始化3个信号量 */
result = rt_sem_init(&sem_lock , "lock",1, RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
goto _error;
result = rt_sem_init(&sem_empty,"empty", MAXSEM, RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
goto _error;
result = rt_sem_init(&sem_full , "full",0, RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
goto _error;
2.创建三个线程,producer的优先级为10。consumer1和consumer2的优先级11。代码省略。
看看生产者入口函数
生产者线程需要先获取sem_empty,当成功获取,这表示缓冲区有空位,即可以向缓冲区投放“产品”。注意修改缓冲区的代码为临界区,因此使用二值信号量sem_lock将整个临界区保护起来。
------消费者入口
为了减少代码,两个消费者采用相同的线程入口函数。通过参数parameter来区分开来
消费者线程需要获取sem_full信号量,如果其值为0则意味着缓冲区没有产品。该线程无妨获取信号量而被阻塞。如果成功获取sem_full信号量,表示缓冲区非空,即可取出产品消费。之后,执行释放sem_emptry,即缓冲区空位数目增加一个。
3.两个消费者之间的轮询。
由于两个消费者的优先级相同,只能按照时间片轮换运行。这两个线程执行情况分析起来挺复杂的,现在我也没有彻底的分析清楚。
总结:用二值信号量实现保护临界区不被破坏,用计数信号量,实现多个线程间的同步。
这里有一个解决哲学家就餐问题的例子,我看的也不是太明白了,所以就不细研究了,详细请看官方文档:
http://www.rt-thread.org/phpBB3/viewtopic.php?f=28&t=1909
5---互斥锁
互斥锁跟二值信号量很相似,RT-Thread中互斥锁也有静态和动态之分,和互斥锁有关的操作如下:
初始化——rt_mutex_init()(对应静态互斥锁)
建立——rt_mutex_create()(对应动态互斥锁)
获取——rt_mutex_take();
释放——rt_mutex_release();
脱离——rt_mutex_detach()(对应静态互斥锁)
删除——rt_mutex_delete()(对应动态互斥锁)
互斥锁跟信号量的区别是:
1.信号量哪里都可以释放,但互斥锁只有获得了其控制权的线程才可以释放,即:只有“锁上”它的那个线程才有“钥匙”打开它。
2.信号量可能导致线程优先级反转,而互斥锁可通过优先级继承的方法解决优先级反转的问题。
详细请看官方文档和例程:
http://www.rt-thread.org/phpBB3/viewtopic.php?f=28&t=1910
如果有人细细的研究RT-Thread的互斥锁的内核源码,请看源码分析:
http://blog.csdn.net/flydream0/article/details/8592727
6---邮箱
RT-Thread的邮箱中共可存放固定条数的邮件,邮箱容量在创建时设定,每个邮件大小为4个字节(正好是一个指针的大小)。
发送线程向邮箱中发送邮件,等待线程接收邮件,如果等待接收到邮件则得以执行,否则等待。
邮件的操作跟上面的相似,也有动态和静态之分。
7-消息队列
这个跟邮箱差不多,而邮件只能容纳固定的4字节内容,消息队列能够接收不固定长度的消息。
总结:在使用IPC对象时,在其等待获取资源时,都会早餐线程阻塞(除非以非等待的方式获取)所以,不要在中断服务程序中去尝试去执行获取信号量、互斥锁、邮箱、消息队列的操作。