对于目前主流的RTOS的任务,大部分都属于并发的线程。
因为MCU上的资源每个任务都是共享的,可以认为是单进程多线程模型。
【freertos】003-任务基础知识
在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。
多任务访问全局变量会带来共享资源管理问题,消息队列最终是用的全局变量!但是消息队列对这个全局变量做了保护,重点就是资源管理的保护!假如你直接使用全局变量,那么在代码中任何任务都可以随时随地的访问、修改这个全局变量!
A任务正在使用全局变量S,A任务由于任务切换暂停运行切换到B任务,而B任务也要使用S,这时候B任务修改了S的值。当再次切换到A任务的时候这个变量S就变了,A任务可能就运行出错。
如果使用消息队列的话,A任务要使用队列S,先申请,申请成功以后才可以使用。B任务也要使用S的时候也要先申请,当时发现S已经被A任务使用了,所以B任务就没法使用(假设当前的队列长度为1),直到A任务使用完S并且释放掉B任务才申请使用!
作用:任务与任务,任务与中断,任务与多个任务之间发送消息
全局数组 相比消息队列,主要有如下四个问题:
使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组无法做到,任务的超时等机制需要用户自己实现。
使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
FIFO 机制更有利于数据的处理。
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资
源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任
务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
机理与消息队列类似,去除了存储空间,只保留了数据结构
抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它
当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。
二值信号量通常用来作为任务间的同步
计数信号量以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源
数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统
没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0
的时候任务就无法访问该资源了。
互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不
同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资
源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。
作用:事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能
是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。
即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;
也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个
事件。
全局变量相比事件标志组,主要有如下三个问题:
使用事件标志组可以让 RTOS 内核有效地管理任务,而全局变量无法做到,任务的超时等机制需要用户自己实现。
使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
对于进程来说,子进程是父进程的复制品,从父进程那里获得父进程的数据空间,堆和栈的复制品。
而线程,相对于进程而言,是一个更加接近于执行体的概念,可以和同进程的其他线程之间直接共享数据,而且拥有自己的栈空间,拥有独立序列。
共同点:它们都能提高程序的并发度,提高程序运行效率和响应时间。线程和进程在使用上各有优缺点。线程执行开销比较小,但不利于资源的管理和保护,而进程相反。同时,线程适合在SMP机器上运行,而进程可以跨机器迁移。
他们之间根本区别在于 多进程中每个进程有自己的地址空间,线程则共享地址空间。所有其他区别都是因为这个区别产生的。比如说:
速度。线程产生的速度快,通讯快,切换快,因为他们处于同一地址空间。
线程的资源利用率好。
线程使用公共变量或者内存的时候需要同步机制,但进程不用。
而他们通信方式的差异也仍然是由于这个根本原因造成的。
通信方式之间的差异
因为那个根本原因,实际上只有进程间需要通信,同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。
而进程间通信无论是信号,管道pipe还是共享内存都是由操作系统保证的,是系统调用.
管道( pipe ):
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道 (namedpipe) :
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量(semophore ) :
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( messagequeue ) :
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。也可以作为线程之间通信
信号 (sinal ) :
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存(shared memory ) :
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字(socket ) :
套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。
锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
信号量机制(Semaphore):
包括无名线程信号量和命名线程信号量
信号机制(Signal):
类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
信号量机制(Semaphore):
信号机制(Signal):
消息队列( messagequeue ) :
linux条件变量使用和与信号量的区别
1.条件变量同步机制
让进入管程却因资源不足而阻塞的进程暂时放弃管程控制权(开放管程),进入该条件变量的等待队列条件变量只能在管程中通过两个原语操作——wait原语和signal原语。
一个进程已进入管程但无法继续执行,便在相应的条件变量x上调用x.wait(
),将自己阻塞并移入x的等待队列中,放弃管程控制权(开放管程),另一进程可以通过对同一个条件变量执行x.signal(
)来唤醒之前在x上等待的进程
条件变量仅起到维护等待队列的作用,不存在相关的值,也不能象信号量那样加减累计。
与RTOS的事件作用差不多
2.互斥锁
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
在Posix Thread中定义有一套专门用于线程同步的mutex()函数。
(1)最大的区别应该是使用条件变量可以一次唤醒所有等待者,但信号量不行。
(2)信号量有一个表示状态的值,而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。因为信号量有一个状态,如果想精准的同步,那么信号量可能会有特殊的地方。信号量可以解决条件变量中存在的唤醒丢失问题。
(3)在Posix.1基本原理一文声称,有了互斥锁和条件变量还提供信号量的原因是:“本标准提供信号量的主要目的是提供一种进程间同步的方式,这些进程可能共享也可能不共享内存区。互斥锁和条件变量是作为线程间的同步机制说明的,这些线程总是共享(某个)内存区。”尽管信号量的意图在于进程间同步,互斥锁和条件变量的意图在于线程间同步,但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。信号量最有用的场景是用以指明可用资源的数量。
经典的一句话:
互斥量是信号量的一种特例,互斥量的本质是一把锁。A mutex is basically a lock that we set (lock) before accessing a shared resource and release (unlock) when we’re done
条件变量是什么
本身不是锁,满足某个条件,像加锁一样,造成阻塞,与互斥量配合,给多线程提供会所。
为什么要用条件变量:
在线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待其他线程唤醒他;一旦条件满足,线程就可以被唤醒,并且拿互斥锁去访问共享区。经过这中设计能让进程运行更稳定。
典型应用场景:生产者和消费者模型
Linux基础——线程同步之条件变量
为什么要用互斥锁
互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
linux c 线程同步的三种方法
互斥锁、自旋锁、原子操作的原理、区别及应用场景
原理:
互斥锁属于sleep-waiting类型的锁,例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,Core0会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其它的任务而不必进行忙等待。
自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。
互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。
两种锁适用于不同场景:
如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。
如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。
如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。
如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。