多线程,作为实现软件并发执行的一个重要的方法,也开始具有越来越重要的地位。这一节我们主要来介绍线程的相关知识。

一、线程创建与删除

线程创建分为两种方式:静态创建与动态创建;

1、动态创建

优点:创建方便,内存由用户创建于释放;

缺点:运行时需要动态分配,效率较低;

rt_thread_create

这个函数将创建一个线程对象并分配线程对象内存;

/**
 * @brief   This function will create a thread object and allocate thread object memory.
 *          and stack.
 *        
 * @param   name is the name of thread, which shall be unique.
 *          //新建线程的名称:字符指针;
 * @param   entry is the entry function of thread.
 *          //线程的函数指针,也是所创建线程所执行的任务;(线程的入口函数)
 * @param   parameter is the parameter of thread enter function.
 *          //函数指针的参数;
 * @param   stack_size is the size of thread stack.
 *          //线程堆栈大小
 * @param   priority is the priority of thread.
 *          //线程优先级
 * @param   tick is the time slice if there are same priority thread.
 *          //线程时间片;
 * @return  If the return value is a rt_thread structure pointer, the function is successfully executed.
 *          If the return value is RT_NULL, it means this operation failed.
             //返回值:如果返回值是rt_thread结构指针,则函数执行成功。
 *                    如果返回值为RT_NULL,则表示操作失败。
 */        

rt_thread_t rt_thread_create(const char *name,
                             void (*entry)(void *parameter),
                             void       *parameter,
                             rt_uint32_t stack_size,
                             rt_uint8_t  priority,
                             rt_uint32_t tick);

线程创建案例:

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
 rt_thread_t th_ptr;//定义一个结构体指针变量;

void th1_printf(void *parameter)//线程rt的入口函数,函数没有参数;
{
    while(1)                    //函数要执行的内容,延迟打印;
    {
    rt_kprintf("th1 is running\r\n");
    rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;
    th_ptr=rt_thread_create("rt", th1_printf, NULL, 1024, 20, 10);//动态创建线程;
    if(th_ptr==RT_NULL)//判断线程是否创建成功;
    {    
        LOG_E("RT-Thread create fail!\r\n");
        return -RT_ENOMEM;
        
    }
    rt_thread_startup(th_ptr);//线程启动
    while (count++)
    {
        LOG_D("Hello RT-Thread!");//主函数打印的内容;
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果:

2、静态创建

    rt_err_t rt_thread_init    

        这个函数将初始化一个线程,它被用来初始化静态线程对象。 

/**
 * @brief   This function will initialize a thread. It's used to initialize a
 *          static thread object.
 *        
 * @param   thread is the static thread object.
 *           静态线程的对象,是一个传入参数,参数类型是结构指针;
 * @param   name is the name of thread, which shall be unique.
 *            线程的名字,字符指针形式;
 * @param   entry is the entry function of thread.
 *            线程函数的入口,是一个函数指针,也就是一个函数名;
 * @param   parameter is the parameter of thread enter function.
 *             线程函数的参数;
 * @param   stack_start is the start address of thread stack.
 *            线程堆栈的起始地址;
 * @param   stack_size is the size of thread stack.
 *            线程堆栈的大小;
 * @param   priority is the priority of thread.
 *            线程的优先级;
 * @param   tick is the time slice if there are same priority thread.
 *            线程的时间片。
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
               返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *                         如果返回值为其他值,则表示操作失败
 */
rt_err_t rt_thread_init(struct rt_thread *thread,
                        const char       *name,
                        void (*entry)(void *parameter),
                        void             *parameter,
                        void             *stack_start,
                        rt_uint32_t       stack_size,
                        rt_uint8_t        priority,
                        rt_uint32_t       tick)

线程案例:

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_thread_t th_ptr;
struct rt_thread th2_ptr;
rt_uint8_t th2_stack[1024];
void th2_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("th2 is running\r\n");
        rt_thread_mdelay(1000);
    }
}
void th1_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("th1 is running\r\n");
        rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;
    th_ptr=rt_thread_create("rt", th1_printf, NULL, 1024, 20, 10);
    if(th_ptr==RT_NULL)
    {
        LOG_E("RT-Thread1 create fail!\r\n");
        return -RT_ENOMEM;

    }

  int ret =  rt_thread_init(&th2_ptr,"th2",th2_printf,NULL,th2_stack,1024,20,10);
  if(ret!=RT_EOK)
  {
      LOG_E("RT-Thread2 create fail!\r\n");
      return ret;    
  }
  rt_thread_startup(&th2_ptr);
    rt_thread_startup(th_ptr);
    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果:

3、线程删除 

 线程删除函数:

        静态删除:rt_thread_detach;

        动态删除:rt_thread_delete;

/**
 * @brief   This function will detach a thread. The thread object will be removed from
 *          thread queue and detached/deleted from the system object management.
 *            此函数将静态线程进行删除。
 * @param   thread is the thread to be deleted.
 *            要删除的线程,是一个结构体指针函数;
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 *           return返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *                              如果返回值为其他值,则表示操作失败。
 */            
rt_err_t rt_thread_detach(rt_thread_t thread)

/**
 * @brief   This function will delete a thread. The thread object will be removed from
 *          thread queue and deleted from system object management in the idle thread.
 *            此函数将动态线程进行删除。
 * @param   thread is the thread to be deleted.
 *            要删除的线程,是一个结构体指针函数;
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 *          return返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *                              如果返回值为其他值,则表示操作失败。
 */
rt_err_t rt_thread_delete(rt_thread_t thread)

 线程取消:

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_thread_t th_ptr;
struct rt_thread th2_ptr;
rt_uint8_t th2_stack[1024];
void th2_printf(void *parameter)
{
     int count =0;
    while(1)
    {

        rt_kprintf("th2 is running\r\n");
        rt_thread_mdelay(1000);
        count ++;
        if(count==10)//线程不能多次删除,只能删除一次。
        {
            rt_thread_delete(th_ptr);
        }
    }
}

void th1_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("th1 is running\r\n");
        rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;
    th_ptr=rt_thread_create("rt", th1_printf, NULL, 1024, 20, 10);
    if(th_ptr==RT_NULL)
    {
        LOG_E("RT-Thread1 create fail!\r\n");
        return -RT_ENOMEM;

    }

  int ret =  rt_thread_init(&th2_ptr,"th2",th2_printf,NULL,th2_stack,1024,20,10);
  if(ret!=RT_EOK)
  {
      LOG_E("RT-Thread2 create fail!\r\n");
      return -RT_ENOMEM;
  }
  rt_thread_startup(&th2_ptr);
    rt_thread_startup(th_ptr);
    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果: 

扩展kt_kprintf函数,其可以打印出线程的函数名以及执行打印的行数;

rt_kprintf("th2 is running\r\n");
  
rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);

rt_kprintf("In %c fuct %s is running!the line is %d!\r\n",_FILE,__FUNCTION__,__LINE__);

4、线程的基础知识

线程状态:

        初始状态、就绪状态、运行状态、挂起状态、关闭状态。 

         初始状态:当线程刚开始创建但还是没有启动的时候,处于初始状态,线程参与调度;

        就绪状态:当进程启动之后,线程处于就绪态,线程按照优先级排队,等待被操作系统执行。

        运行状态:线程正在被运行 ,在单核系统中,只有一个线程被执行,在多核系统中,就不止一个线程在执行;

        挂起状态:也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。

        关闭状态:当线程运行结束时将处于关闭状态。关闭状态的线程也叫僵尸线程,不参与线程的调度。

        线程的状态转变,都有一些对应的函数,在后面我们会介绍线程的相关函数。         

线程栈大小

        线程本质上是创建了一个该线程运行的大小函数,线程栈是用于保存线程的上下文,函数调用过程、局部变量等。

线程优先级

        

        线程中不能有死循环,必须有挂起操作,如果没有挂起操作,操作系统就只执行一个线程,其他线程就没有办法再被执行。 

        首先,较高优先级的线程先被操作系统执行,如果没有挂起就一直没执行;当挂起后,操作系统才能执行较低的优先级进程。当进程的优先级一样时,利用时间片,在相同优先级的线程之间进行切换。也就是说操作系统只允许同一时间只有一个线程处于运行状态。

线程时间片

        在线程优先级相同的情况下,通过时间片来控制相同优先级线程执行时间;假设有2个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,那么当系统中不存在比A优先级高的就绪态线程时,系统会在A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对对 B 线程执行 5 个节拍的时长。

二、线程相关函数

线程休眠

        此处的delay函数与裸机的delay函数是不一样的,裸机的delay函数是将程序停留在次进行空转。调用RT-Thread操作系统中的delay函数会将线程进行挂起,把时间给其他线程运行。

rt_thread_mdelay

rt_thread_sleep

rt_thread_delay

mdelay与delay本质上都是调用的sleep函数;所以三个函数的功能是相同的;

/**
 * @brief   This function will let current thread sleep for some ticks. Change current thread state to suspend,
 *          when the thread timer reaches the tick value, scheduler will awaken this thread.
 *
 * @param   tick is the sleep ticks.
 *
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 */
rt_err_t rt_thread_sleep(rt_tick_t tick)
/*这个函数会让当前线程休眠一段时间。将当前线程状态更改为挂起,
    当线程计时器达到tick值时,调度器将唤醒该线程。
    tick是睡眠tick。
    return返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
*如果返回值为其他值,则表示操作失败。*/

        其本质调用了 rt_thread_suspend_with_flag,将对应的线程进行了挂起操作。然后开启一个定时器进行计时,等时间到了之后,会 解除挂起,进行唤醒。

挂起和唤醒线程

rt_thread_suspend 

rt_thread_resume

一般不会直接进行函数的使用,而是在其他函数的底层再去调用这些函数;

挂起函数的本质:

/**
 * @brief   This function will resume a thread and put it to system ready queue.
 *
 * @param   thread is the thread to be resumed.
 *
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 */
rt_err_t rt_thread_resume(rt_thread_t thread)

/**
 * @brief   This function will suspend the specified thread and change it to suspend state.
 *            这个函数将挂起指定的线程并将其更改为挂起状态。
 * @note    This function ONLY can suspend current thread itself.
 *              rt_thread_suspend(rt_thread_self());
 *            这个函数只能挂起当前线程本身
 *          Do not use the rt_thread_suspend to suspend other threads. You have no way of knowing what code a
 *          thread is executing when you suspend it. If you suspend a thread while sharing a resouce with
 *          other threads and occupying this resouce, starvation can occur very easily.
 *
 * @param   thread the thread to be suspended.被挂起的线程。
 * @param   suspend_flag status flag of the thread to be suspended.
 *            要挂起的线程的状态标志。
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 *          返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *        如果返回值为其他值,则表示操作失败。
rt_err_t rt_thread_suspend_with_flag(rt_thread_t thread, int suspend_flag)

 唤醒函数的本质:

/**
 * @brief   This function will resume a thread and put it to system ready queue.
 *            这个函数将恢复一个线程并把它放到系统就绪队列中。
 * @param   thread is the thread to be resumed.
 *            线程是要恢复的线程。
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 *           return返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *            如果返回值为其他值,则表示操作失败。
 */
rt_err_t rt_thread_resume(rt_thread_t thread)

         将对应的线程插入在线程就绪链表中去。

获取当前线程

rt_thread_self

该线程没有参数,只有一个返回值,返回线程的句柄;结构体指针;

/**
 * @brief   This function will return self thread object.
 *
 * @return  The self thread object.
 */
rt_thread_t rt_thread_self(void)

 案例:

        获取线程的信息;

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

rt_thread_t th1_ptr,th2_ptr;

void th1_printf(void *parameter)
{
    rt_thread_t current_ptr;
    while(1)
    {
        current_ptr=rt_thread_self();
        rt_kprintf("the thread of name:%s\r\n",current_ptr->parent.name);
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

void th2_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;

    th1_ptr=rt_thread_create("th1", th1_printf, NULL, 1024, 20, 10);
    if(th1_ptr==RT_NULL)
       {
           LOG_E("RT-Thread1 create fail!\r\n");
           return -RT_ENOMEM;
       }

    th2_ptr=rt_thread_create("th2", th2_printf, NULL, 1024, 20, 10);
        if(th1_ptr==RT_NULL)
           {
               LOG_E("RT-Thread2 create fail!\r\n");
               return -RT_ENOMEM;
           }

     rt_thread_startup(th1_ptr);
     rt_thread_startup(th2_ptr);
    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果: 

线程切换

rt_schedule:让出CPU但是不会重新去排队;直接让出,比他优先级高的既可以被执行了。(较高优先级切换)

rt_thread_yield:调用此函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。也就是说这个会把自己调出,让相同优先级的进行,然后自己在运行。(同种优先级切换)。

线程切换钩子函数

rt_scheduler_sethook完成线程的切换,参数是一个函数指针:把一个函数指针传进来,他就会调用函数指针。

空闲线程勾子函数

rt_thread_idle_sethook

rt_thread_idle_delhook

/**
 * @brief This function sets a hook function to idle thread loop. When the system performs
 *        idle loop, this hook function should be invoked.
 *        这个函数设置一个钩子函数为空闲线程循环。当系统执行空闲循环,这个钩子函数应该被调用。
 * @param hook the specified hook function.
 *        钩子指定的钩子函数
 * @return RT_EOK: set OK.
 *         -RT_EFULL: hook list is full.
 *    RT_EOK:设置OK。RT_EFULL:钩子列表已满
 * @note the hook function must be simple and never be blocked or suspend.
 *        钩子函数必须简单,不能被阻塞或挂起。
 */
rt_err_t rt_thread_idle_sethook(void (*hook)(void))

其他线程都有挂起,这就导致空闲线程不停的在打印。

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

rt_thread_t th1_ptr,th2_ptr;

 void scheduler_hook_func(struct rt_thread *from, struct rt_thread *to)
 {
     rt_kprintf("the thread scheduler of from %s to %s \r\n",from->parent.name,to->parent.name);
 }

 void idle_hook_func(void)
 {
     rt_thread_t current_ptr;
     current_ptr=rt_thread_self();
     rt_kprintf("the thread  of name is %s  \r\n",current_ptr->parent.name);
 }

void th1_printf(void *parameter)
{
    rt_thread_t current_ptr;
    while(1)
    {
        current_ptr=rt_thread_self();
        rt_kprintf("the thread of name:%s\r\n",current_ptr->parent.name);
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

void th2_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;

    th1_ptr=rt_thread_create("th1", th1_printf, NULL, 1024, 20, 10);
    if(th1_ptr==RT_NULL)
       {
           LOG_E("RT-Thread1 create fail!\r\n");
           return -RT_ENOMEM;
       }

    th2_ptr=rt_thread_create("th2", th2_printf, NULL, 1024, 20, 10);
        if(th1_ptr==RT_NULL)
           {
               LOG_E("RT-Thread2 create fail!\r\n");
               return -RT_ENOMEM;
           }

     rt_thread_startup(th1_ptr);
     rt_thread_startup(th2_ptr);
     rt_scheduler_sethook(scheduler_hook_func);
    int ret= rt_thread_idle_sethook(idle_hook_func);
     if(ret!=RT_EOK)
     {
         LOG_E("rt_thread_idle_sethook create fail!\r\n");
         return ret;
     }

    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

 控制其他线程函数

rt_thread_control 动态更改线程的优先级

参数:thread 线程句柄;cmd 指示控制命令; arg:控制参数;

/**
 * @brief   This function will control thread behaviors according to control command.
 *            该函数将根据控制命令控制线程的行为。
 * @param   thread is the specified thread to be controlled.
 *            线程是指定的要控制的线程。
 * @param   cmd is the control command, which includes.
 *            CMD是控制命令
 *              RT_THREAD_CTRL_CHANGE_PRIORITY for changing priority level of thread.
 *            用于改变线程的优先级。
 *              RT_THREAD_CTRL_STARTUP for starting a thread.
 *            用于启动一个线程。
 *              RT_THREAD_CTRL_CLOSE for delete a thread.
 *            表示删除线程。
 *              RT_THREAD_CTRL_BIND_CPU for bind the thread to a CPU.
 *            表示将线程绑定到一个CPU。
 * @param   arg is the argument of control command.
 *            是控制命令的参数。
 * @return  Return the operation status. If the return value is RT_EOK, the function is successfully executed.
 *          If the return value is any other values, it means this operation failed.
 *            返回操作状态。如果返回值为RT_EOK,则表示函数执行成功。
 *            如果返回值为其他值,则表示操作失败
 */
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

rt_thread_t th1_ptr,th2_ptr;

 void scheduler_hook_func(struct rt_thread *from, struct rt_thread *to)
 {
     rt_kprintf("the thread scheduler of from %s to %s \r\n",from->parent.name,to->parent.name);
 }

 void idle_hook_func(void)
 {
     rt_thread_t current_ptr;
     current_ptr=rt_thread_self();
     rt_kprintf("the thread  of name is %s  \r\n",current_ptr->parent.name);
 }

void th1_printf(void *parameter)
{
    rt_thread_t current_ptr;
    while(1)
    {
        current_ptr=rt_thread_self();
        rt_kprintf("the thread of name:%s\r\n",current_ptr->parent.name);
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

void th2_printf(void *parameter)
{
    while(1)
    {
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
    }
}

int main(void)
{
    int count = 1;

    th1_ptr=rt_thread_create("th1", th1_printf, NULL, 1024, 20, 10);
    if(th1_ptr==RT_NULL)
       {
           LOG_E("RT-Thread1 create fail!\r\n");
           return -RT_ENOMEM;
       }

    th2_ptr=rt_thread_create("th2", th2_printf, NULL, 1024, 20, 10);
        if(th1_ptr==RT_NULL)
           {
               LOG_E("RT-Thread2 create fail!\r\n");
               return -RT_ENOMEM;
           }

     rt_thread_startup(th1_ptr);
    // rt_thread_startup(th2_ptr);
    // rt_scheduler_sethook(scheduler_hook_func);
//    int ret= rt_thread_idle_sethook(idle_hook_func);
//     if(ret!=RT_EOK)
//     {
//         LOG_E("rt_thread_idle_sethook create fail!\r\n");
//         return ret;
//     }



    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
        if(count==4)
        {
        int  ret= rt_thread_control(th2_ptr, RT_THREAD_CTRL_STARTUP,NULL);
           if(ret!=RT_EOK)
             {
               LOG_E("th2_rt_thread_control fail !\r\n");
                return ret;
             }
         }
        if(count==6)
               {
               int  ret= rt_thread_control(th2_ptr, RT_THREAD_CTRL_CLOSE,NULL);
                  if(ret!=RT_EOK)
                    {
                      LOG_E("th2_rt_thread_control fail !\r\n");
                       return ret;
                    }
                }
    }

    return RT_EOK;
}

 案例,利用控制其他线程函数,将线程th2在其他线程打印3秒后开启,打印两秒后关闭;

 运行结果:

三、线程同步

 1、概念

线程同步:是指多个线程通过特定的机制来控制线程之间的执行顺序,(线程间通过同步建立执行顺序的关系),如果没有同步,线程之间将是无序的。比如三个优先级相同的线程,th1、th2、th3三个线程,先运行哪一个呢?肯定是无须,那如果我们想要求某一个先运行就需要用到,某个机制进行控制。

临界区(CriticalSection)是指在多线程或多进程环境中,访问共享资源或共享数据的一段代码或代码块,需要保证在任意时刻只能有一个线程或进程访问该共享资源,以避免出现竞争条件(Race Condition)和数据不一致的问题。

        保证在任意时刻只能有一个线程或进程访问该共享资源的方式有三个,分别为:关闭中断、禁止调度、互斥特性保护临界区。

        关闭中断:rt_hw_interrupt_disable、rt_hw_interrupt_enable;

        禁止调度:调度各个线程的进行,rt_enter_critical,rt_exit_critical;

        互斥特性保护临界区:用的较多。信号量;互斥量。 

        同步最常见方法是使用锁,锁是一种非强制机制,每一个线程在访问数据或资源之前首先试图获取锁时,并在这访问结束之后释放锁,在锁已经被占用的时候试图获取锁,线程会等待,直到锁重新可用。

2、信号量 

        信号量又称为信号锁,其主要作用也是起到一个阻塞的作用。

        二元信号量是最简单的一种锁,它只有两种状态:占用与非占用,他只适合被唯一一个线程独占访问资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量的线程会获得该锁,并将二元信号量置为占用状态,伺候其他的所有试图获取该二元信号量的线程将会等待,直到该锁被释放。

        对于允许多个线程并发访问的资源,多元信号量简称信号量,它是一个很好的选择。一个初始值为N的信号量,允许N个线程并发访问。线程访问资源的时候首先获取信号量。

        每take一次对信号量的值减1,如果信号量的值减为0,则阻塞。每release一次对信号量的值加1。

        利用信号量控制线程1、线程2的执行,在线程1中对线程2的信号量加1,在线程2中对线程1的信号量加1。

        创建信号量一定要在start前面,要不然线程都准备运行了,信号量还没进去。

线程信号量的创建:

        动态创建:rt_sem_create

        静态创建:rt_sem_init

获取信号量:rt_sem_take

释放信号量:rt_sem_release

信号量的删除:

        删除动态:rt_sem_delete

        删除静态:rt_sem_detach

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_sem_t sem1_ptr ,sem2_ptr;
rt_thread_t th1_ptr,th2_ptr;
void th1_printf(void *parameter)
{
    while(1)
   {
       rt_sem_take(sem1_ptr, RT_WAITING_FOREVER);
       rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
       rt_thread_mdelay(1000);
       rt_sem_release(sem2_ptr);
   }
}
void th2_printf(void *parameter)
{
       while(1)
    {
           rt_sem_take(sem2_ptr, RT_WAITING_FOREVER);
        rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
        rt_thread_mdelay(1000);
        rt_sem_release(sem1_ptr);
    }
}

int main(void)
{
    int count = 1;
    sem1_ptr= rt_sem_create("sem1", 1,  RT_IPC_FLAG_FIFO);
    if(sem1_ptr==RT_NULL)
    {
        LOG_E("SEM1 create fail!\r\n");
                          return -RT_ENOMEM;
    }

    sem2_ptr= rt_sem_create("sem2",0,  RT_IPC_FLAG_FIFO);
      if(sem2_ptr==RT_NULL)
      {
          LOG_E("SEM2 create fail!\r\n");
                            return -RT_ENOMEM;
      }

    th1_ptr=rt_thread_create("th1", th1_printf, NULL, 1024, 20, 10);
            if(th1_ptr==RT_NULL)
               {
                   LOG_E("RT-Thread1 create fail!\r\n");
                   return -RT_ENOMEM;
               }
            rt_thread_startup(th1_ptr);
    th2_ptr=rt_thread_create("th2", th2_printf, NULL, 1024, 10, 10);
             if(th1_ptr==RT_NULL)
                {
                     LOG_E("RT-Thread2 create fail!\r\n");
                     return -RT_ENOMEM;
                }
             rt_thread_startup(th2_ptr);
    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果: 

3、互斥量

        互斥量和二元信号量很类似,资源仅同时允许一个线程访问,但和信号量不同的是,信号量在整个系统可以被任意线程获取并释放,也就是说,同一个信号量可以被系统中的一个线程获取之后,再被另一线程进行释放。而互斥量则要求哪个线程获取了互斥量,哪个线程就要负责释放这个锁,其他线程去释放互斥量是无效的。    

        此外,信号量会造成优先级翻转, 互斥量不会造成优先级翻转。

互斥量的创建:

        动态创建:rt_mutex_create

        静态创建:rt_mutex_init

互斥量的获取:rt_mutex_take

互斥量的释放:rt_mutex_release

互斥量的删除:

        动态删除:rt_event_delete

        静态删除:rt_mutex_detach

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
//线程的入口函数
rt_thread_t th1_ptr,th2_ptr,th3_ptr;
rt_mutex_t mutex1_ptr,mutex2_ptr;
void th1_printf(void *parameter)
{
    while(1)
   {
      rt_mutex_take(mutex1_ptr,RT_WAITING_FOREVER);
      rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
       rt_thread_mdelay(500);
       rt_mutex_release(mutex1_ptr);
   }
}
void th2_printf(void *parameter)
{
       while(1)
    {
        rt_kprintf("func %s th3_ptr prio is %d!\r\n",__FUNCTION__,th3_ptr->current_priority);
        rt_thread_mdelay(100);
    }
}
void th3_printf(void *parameter)
{
       while(1)
    {
           rt_mutex_take(mutex1_ptr,RT_WAITING_FOREVER);
           rt_kprintf("%s is running!the line is %d!\r\n",__FUNCTION__,__LINE__);
                  rt_thread_mdelay(100);
                  rt_mutex_release(mutex1_ptr);
    }
}
int main(void)
{
    int count = 1;
    //互斥量的创建
    mutex1_ptr= rt_mutex_create("mutex1",  RT_IPC_FLAG_PRIO);
    if(mutex1_ptr==RT_NULL)
    {
        LOG_E("mutex1 rt_mutex_create fail!\r\n");
        return -RT_ENOMEM;
    }

    //线程的动态创建
    th1_ptr=rt_thread_create("th1", th1_printf, NULL, 1024, 20, 10);
               if(th1_ptr==RT_NULL)
                  {
                      LOG_E("RT-Thread1 create fail!\r\n");
                      return -RT_ENOMEM;
                  }
               rt_thread_startup(th1_ptr);
     th2_ptr=rt_thread_create("th2", th2_printf, NULL, 1024, 10, 10);
                if(th2_ptr==RT_NULL)
                   {
                        LOG_E("RT-Thread2 create fail!\r\n");
                        return -RT_ENOMEM;
                   }
                rt_thread_startup(th2_ptr);
     th3_ptr=rt_thread_create("th3", th3_printf, NULL, 1024, 24, 10);
                   if(th3_ptr==RT_NULL)
                     {
                          LOG_E("RT-Thread3 create fail!\r\n");
                          return -RT_ENOMEM;
                      }
                rt_thread_startup(th3_ptr);
    while (count++)
    {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

运行结果:

        优先级翻转,打印同一个线程的优先级,优先级不同,表示由于互斥量的存在,造成了优先级发生变化。

4、事件集

        事件集也是线程间同步的机制之一,一个时间集可以包含多个时间,利用事件集可以完成一对多,多对多的线程间同步。

        事件集工作机制

        事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多。多对多的同步,即一个线程与多个事件的关系可以设置为:其中任意一个事件唤醒线程,或几个事件到达后才唤醒线程进行后续的处理;同样时间也可以是多个线程同步多个事件。这种多个事件的集合可以用一个32位无符号整型变量来表示,变量的每一位代表一个事件,线程通过“逻辑与”或“逻辑或”将一个或多个事件关联起来,形成事件组合。

事件只与线程相关,事件间相互独立,每个线程可拥有32个事件标志位,采用一个32bit无符号整型数进行记录,每一个bit代表一个事件;

事件仅用于同步,不提供数据传输功能。

事件无排队性,即多次向线程发送同一事件,其效果等同于只发送一次。

每个线程都拥有一个事件信息标记:

接收方式:逻辑与 RT_EVENT_FLAG_AND;逻辑或RT_EVENT_FLAG_OR;清除标记RT_EVENT_FLAG_CLEAR。

事件集的使用:

事件集创建:

        动态创建:rt_event_create

        静态创建:rt_event_init

事件集发送:rt_event_send

事件集接收:rt_event_recv

事件集删除:

        动态删除:rt_event_delete

        静态删除:rt_event_detach

#include <rtthread.h>

#define THREAD_PRIORITY      9
#define THREAD_TIMESLICE     5

#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)

/* 事件控制块 */
static struct rt_event event;

rt_align(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;

/* 线程 1 入口函数 */
static void thread1_recv_event(void *param)
{
    rt_uint32_t e;

    /* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */
    if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
                      RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                      RT_WAITING_FOREVER, &e) == RT_EOK)
    {
        rt_kprintf("thread1: OR recv event 0x%x\n", e);
    }

    rt_kprintf("thread1: delay 1s to prepare the second event\n");
    rt_thread_mdelay(1000);

    /* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */
    if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
                      RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
                      RT_WAITING_FOREVER, &e) == RT_EOK)
    {
        rt_kprintf("thread1: AND recv event 0x%x\n", e);
    }
    rt_kprintf("thread1 leave.\n");
}


rt_align(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* 线程 2 入口 */
static void thread2_send_event(void *param)
{
    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_thread_mdelay(200);

    rt_kprintf("thread2: send event5\n");
    rt_event_send(&event, EVENT_FLAG5);
    rt_thread_mdelay(200);

    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_kprintf("thread2 leave.\n");
}

int event_sample(void)
{
    rt_err_t result;

    /* 初始化事件对象 */
    result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO);
    if (result != RT_EOK)
    {
        rt_kprintf("init event failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_recv_event,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_send_event,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);

 运行结果:

        线程1接收事件,线程2发送事件,第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 。第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志。

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐