内核7-线程间同步

2023-11-04

目录

1、信号量

1.1、信号量机制

1.2、信号量的使用场合

1.2.1、线程同步

1.2.2、锁

1.2.3、中断与线程的同步

1.2.4、资源计数

1.3、信号量控制块

1.4、函数

1.4.1、rt_sem_init()函数

1.4.2、rt_sem_create()函数

1.4.3、rt_sem_detach()函数

1.4.4、rt_sem_delete()函数

1.4.5、rt_sem_take()函数

1.4.6、rt_sem_trytake()函数

1.4.7、rt_sem_release()函数

1.4.8、rt_sem_control()函数

2、互斥量

2.1、互斥量机制

2.1.1、优先级反转

2.2、优先级继承算法

2.3、互斥量的使用场合

2.4、互斥量控制块

2.5、函数

2.5.1、rt_mutex_init()函数

2.5.2、rt_mutex_create()函数

2.5.3、rt_mutex_detach()函数

2.5.4、rt_mutex_delete()函数

2.5.5、rt_mutex_take()函数

2.5.6、rt_mutex_release()函数

2.5.7、rt_mutex_control()函数

3、事件集

3.1、事件集机制

3.1.1、事件集特点

3.2、事件集控制块

3.3、函数

3.3.1、rt_event_init()函数

3.3.2、rt_event_create()函数

3.3.3、rt_event_detach()函数

3.3.4、rt_event_delete()函数

3.3.5、rt_event_send()函数

3.3.6、rt_event_recv()函数

3.3.7、rt_event_control()函数


1、信号量

1.1、信号量机制

信号量值使用 16位的无符号整数表示。

1.2、信号量的使用场合

信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。

1.2.1、线程同步

线程同步是信号量最简单的一类应用。例如,使用信号量进行两个线程之间的同步,信号量的值初始化成 0,表示具备 0 个信号量资源实例;而尝试获得该信号量的线程,将直接在这个信号量上进行等待。

当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。

1.2.2、锁

锁,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问。信号量在作为锁来使用时,通常应将信号量资源实例初始化成 1,代表系统默认有一个资源可用,因为信号量的值始终在 1 和 0 之间变动,所以这类锁也叫做二值信号量。

注:二值信号量不适合用作锁,因为存在优先级反转的问题。

1.2.3、中断与线程的同步

信号量也能够方便地应用于中断与线程间的同步,例如一个中断触发,中断服务例程需要通知线程进行相应的数据处理。这个时候可以设置信号量的初始值是 0,线程在试图持有这个信号量时,由于信号量的初始值是 0,线程直接在这个信号量上挂起直到信号量被释放。当中断触发时,先进行与硬件相关的动作,例如从硬件的 I/O 口中读取相应的数据,并确认中断以清除中断源,而后释放一个信号量来唤醒相应的线程以做后续的数据处理。

注:中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。

1.2.4、资源计数

信号量也可以认为是一个递增或递减的计数器,需要注意的是信号量的值非负。例如:初始化一个信号量的值为 5,则这个信号量可最大连续减少 5 次,直到计数器减为 0。资源计数适合于线程间工作处理速度不匹配的场合,这个时候信号量可以做为前一线程工作完成个数的计数,而当调度到后一线程时,它也可以以一种连续的方式一次处理多个事件。例如,生产者与消费者问题中,生产者可以对信号量进行多次释放,而后消费者被调度到时能够一次处理多个信号量资源。

注:一般资源计数类型多是混合方式的线程间同步,因为对于单个的资源处理依然存在线程的多重访问,这就需要对一个单独的资源进行访问、处理,并进行锁方式的互斥操作。

1.3、信号量控制块

信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 structrt_semaphore 表示。

struct rt_semaphore
{
	struct rt_ipc_object parent;                        /* ipc对象 */

    rt_uint16_t          value;                         /* 信号量值 */
    rt_uint16_t          reserved;                      /* 保留字段 */
};
typedef struct rt_semaphore *rt_sem_t;

1.4、函数

1.4.1、rt_sem_init()函数

此函数将初始化一个信号量,并将其交由对象容器管理。

1)初始化信号量对象

2)初始化IPC信号

3)设置信号量值和标志(RT_IPC_FLAG_FIFO/RT_IPC_FLAG_PRIO)

rt_err_t rt_sem_init(rt_sem_t    sem,
						const char *name,
						rt_uint32_t value,
						rt_uint8_t  flag)
{
	RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
	RT_ASSERT(value < 0x10000U); //断言value < 0x10000U(65536)
   
    rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name); //初始化信号量对象

    rt_ipc_object_init(&(sem->parent)); //初始化ipc对象

    sem->value = (rt_uint16_t)value; //设置初始值

    sem->parent.parent.flag = flag; //设置信号量标志

    return RT_EOK; //返回RT_EOK
}

1.4.2、rt_sem_create()函数

此函数将从内存中分配一个信号量。

1)分配信号量对象

2)初始化IPC信号

3)设置信号量值和标志(RT_IPC_FLAG_FIFO/RT_IPC_FLAG_PRIO)

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)
{
    rt_sem_t sem;

    RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用
    RT_ASSERT(value < 0x10000U); //断言value < 0x10000U(65536)

    sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name); //分配信号量对象
    
    if (sem == RT_NULL) //sem等于RT_NULL
        return sem; //返回RT_NULL

    rt_ipc_object_init(&(sem->parent)); //初始化信号量ipc对象

    sem->value = value; //设置信号量初始值

    sem->parent.parent.flag = flag; //设置信号量的标志

    return sem; //返回信号量
}

1.4.3、rt_sem_detach()函数

此函数用于卸载信号量。

1)唤醒所有等待此信号量的线程

2)脱离信号量对象

rt_err_t rt_sem_detach(rt_sem_t sem)
{
    /* 参数检查 */
    RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
    RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent)); //断言信号量对象是静态的

    rt_ipc_list_resume_all(&(sem->parent.suspend_thread)); //唤醒等待此信号量的线程

    rt_object_detach(&(sem->parent.parent)); //脱离信号量对象

    return RT_EOK; //返回RT_EOK
}

1.4.4、rt_sem_delete()函数

此函数将删除一个信号量对象并释放内存。

1)唤醒所有等待此信号量的线程

2)删除信号量对象

rt_err_t rt_sem_delete(rt_sem_t sem)
{
    RT_DEBUG_NOT_IN_INTERRUPT; //断言不在ISR中调用

    /* 参数检查 */
    RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
    RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent) == RT_FALSE); //断言信号量对象不是静态的

    
    rt_ipc_list_resume_all(&(sem->parent.suspend_thread)); //唤醒所有等待此信号量的线程

    rt_object_delete(&(sem->parent.parent)); //删除信号量对象

    return RT_EOK; //返回RT_EOK
}

1.4.5、rt_sem_take()函数

这个函数将接受一个信号量,如果这个信号量不可用,线程将等待指定的时间。

1)调用钩子回调函数rt_object_trytake_hook

2)信号量大于0,则信号量减1后直接返回

3)非阻塞调用,则直接返回-RT_ETIMEOUT

4)获取当前线程,并将线程的错误码设置为RT_EOK

5)挂起线程

6)设置线程定时器(线程超时函数会将线程从挂起链表移除,并将线程加入到就绪链表中)

7)调度

8)调用钩子回调函数rt_object_take_hook

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* 参数检查 */
    RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent))); //回调函数

    temp = rt_hw_interrupt_disable(); //关中断

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value)); //打印信息

    if (sem->value > 0) //信号量值大于0
    {
        sem->value --; //信号量值减1

        rt_hw_interrupt_enable(temp); //开中断
    }
    else 
    {
        if (time == 0) //非阻塞调用
        {
            rt_hw_interrupt_enable(temp); //开中断

            return -RT_ETIMEOUT; //返回-RT_ETIMEOUT
        }
        else
        {
            RT_DEBUG_IN_THREAD_CONTEXT; //当前上下文检查

            thread = rt_thread_self(); //获取当前线程

            thread->error = RT_EOK; //将线程的错误码设置为RT_EOK

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n",
                                        thread->name)); //打印信息

            rt_ipc_list_suspend(&(sem->parent.suspend_thread),
                                thread,
                                sem->parent.parent.flag); //挂起线程

            /* 有超时时间,启动线程定时器 */
            if (time > 0) 
            {
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
                                            thread->name)); //打印信息

                rt_timer_control(&(thread->thread_timer),
                                 RT_TIMER_CTRL_SET_TIME,
                                 &time); //设置线程定时器超时时间
                rt_timer_start(&(thread->thread_timer)); //重启线程定时器
            }

            rt_hw_interrupt_enable(temp); //开中断

            rt_schedule(); //调度

            if (thread->error != RT_EOK) //线程错误码不等于RT_EOK
            {
                return thread->error; //返回线程错误码
            }
        }
    }

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent))); //调用回调函数

    return RT_EOK; //返回RT_EOK
}

1.4.6、rt_sem_trytake()函数

此函数将尝试获取一个信号量并立即返回。

rt_err_t rt_sem_trytake(rt_sem_t sem)
{
	return rt_sem_take(sem, 0); //超时时间参数为0
}

1.4.7、rt_sem_release()函数

此函数将释放一个信号量,如果有线程挂起这个信号量,它将被唤醒。

1)调用钩子回调函数rt_object_put_hook

2)如果挂起链表非空,则唤醒挂起链表中第一个线程

3)如果挂起链表为空,信号量加1

4)如果需要进行调度,则进行调度

rt_err_t rt_sem_release(rt_sem_t sem)
{
    register rt_base_t temp;
    register rt_bool_t need_schedule;

    /* 参数检查 */
    RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent))); //调用回调函数

    need_schedule = RT_FALSE; //设置need_schedule为RT_FALSE

    temp = rt_hw_interrupt_disable(); //关中断

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s releases sem:%s, which value is: %d\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value)); //打印信息

    if (!rt_list_isempty(&sem->parent.suspend_thread)) //挂起链表非空
    {
        rt_ipc_list_resume(&(sem->parent.suspend_thread)); //恢复挂起线程
        need_schedule = RT_TRUE; //需要调度
    }
    else
        sem->value ++; //信号量计数器加1

    rt_hw_interrupt_enable(temp); //开中断

    if (need_schedule == RT_TRUE) //是否需要调度
        rt_schedule(); //调度

    return RT_EOK; //返回RT_EOK
}

1.4.8、rt_sem_control()函数

此函数可以获取或设置信号量对象的一些额外属性,目前只有重置信号量这一个功能。

rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg)
{
    rt_ubase_t level;

    /* 参数检查 */
    RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量

    if (cmd == RT_IPC_CMD_RESET) //重置
    {
        rt_uint32_t value;

        value = (rt_uint32_t)arg; //获取值

        level = rt_hw_interrupt_disable(); //关中断

        rt_ipc_list_resume_all(&sem->parent.suspend_thread); //唤醒所有等待该信号量的线程

        sem->value = (rt_uint16_t)value; //将信号量计数器设置为新的值

        rt_hw_interrupt_enable(level); //开中断

        rt_schedule(); //调度

        return RT_EOK; //返回RT_EOK
    }

    return -RT_ERROR; //返回-RT_ERROR
}

2、互斥量

2.1、互斥量机制

互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。可以解决二值信号量存在的优先级反转问题。

2.1.1、优先级反转

当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。

如下图所示:有优先级为 A、 B 和 C 的三个线程,优先级 A> B > C。线程A, B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。

2.2、优先级继承算法

在RT-Thread操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承算法。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

2.3、互斥量的使用场合

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。

2.4、互斥量控制块

互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体 struct rt_mutex 表示。

struct rt_mutex
{
    struct rt_ipc_object parent;                        /* ipc对象 */

    rt_uint16_t          value;                         /* 互斥量值 */

    rt_uint8_t           original_priority;             /* 拥有互斥量线程原先的优先级 */
    rt_uint8_t           hold;                          /* 线程持有互斥量的数量 */

    struct rt_thread    *owner;                         /* 当前拥有互斥量的线程 */
};
typedef struct rt_mutex *rt_mutex_t;

2.5、函数

2.5.1、rt_mutex_init()函数

此函数将初始化互斥锁,并将交由对象容器管理。

1)初始化IPC互斥量对象

2)初始化互斥量对象参数

rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
{
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL

    rt_object_init(&(mutex->parent.parent), RT_Object_Class_Mutex, name); //初始化互斥量对象

    rt_ipc_object_init(&(mutex->parent)); //初始化ipc对象

    mutex->value = 1; //设置互斥量值为1
    mutex->owner = RT_NULL; //设置互斥量owner为RT_NULL
    mutex->original_priority = 0xFF; //设置互斥量original_priority为0xff
    mutex->hold  = 0; //设置欲持有互斥锁的线程数为0

    mutex->parent.parent.flag = flag; //设置互斥量的标志

    return RT_EOK; //返回RT_EOK
}

2.5.2、rt_mutex_create()函数

此函数将从内存中分配一个互斥锁对象。

1)分配互斥量对象

2)初始化IPC互斥量对象

3)初始化互斥量对象参数

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
{
    struct rt_mutex *mutex;

    RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用

    mutex = (rt_mutex_t)rt_object_allocate(RT_Object_Class_Mutex, name); //分配互斥量对象
    if (mutex == RT_NULL) //mutex等于RT_NULL
        return mutex; //返回mutex

    rt_ipc_object_init(&(mutex->parent)); //初始化互斥量ipc对象

    mutex->value = 1; //设置互斥量值为1
    mutex->owner = RT_NULL; //设置互斥量拥有者为RT_NULL
    mutex->original_priority = 0xFF; //设置原优先级值为0xff
    mutex->hold  = 0; //设置欲持有互斥锁的线程数为0

    mutex->parent.parent.flag = flag; //设置互斥量对象标志

    return mutex; //返回互斥量
}

2.5.3、rt_mutex_detach()函数

此函数将把互斥锁从资源管理中分离出来。

1)唤醒所有等待此互斥量的线程

2)脱离互斥量对象

rt_err_t rt_mutex_detach(rt_mutex_t mutex)
{
    /* 参数检查 */
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
    RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断对象为互斥量
    RT_ASSERT(rt_object_is_systemobject(&mutex->parent.parent)); //断言对象为静态对象

    rt_ipc_list_resume_all(&(mutex->parent.suspend_thread)); //唤醒所有等待此互斥量的线程

    rt_object_detach(&(mutex->parent.parent)); //脱离互斥量对象

    return RT_EOK; //返回RT_EOK
}

2.5.4、rt_mutex_delete()函数

此函数将删除一个互斥对象并释放内存。

1)唤醒所有等待此互斥量的线程

2)删除互斥量对象

rt_err_t rt_mutex_delete(rt_mutex_t mutex)
{
    RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用

    /* 参数检查 */
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
    RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量
    RT_ASSERT(rt_object_is_systemobject(&mutex->parent.parent) == RT_FALSE); //断言对象不是静态的

    rt_ipc_list_resume_all(&(mutex->parent.suspend_thread)); //唤醒所有等待此互斥量的线程
 
    rt_object_delete(&(mutex->parent.parent)); //删除互斥量对象

    return RT_EOK; //返回RT_EOK
}

2.5.5、rt_mutex_take()函数

此函数将获得一个互斥锁,如果互斥锁不可用,线程将等待指定的时间。

1)获取当前线程

2)调用钩子回调函数rt_object_trytake_hook

3)将线程错误码设置为RT_EOK

4)线程已拥有互斥量,互斥量的hold成员加1(支持当前线程获取多次互斥量)

5)互斥量可用时,设置互斥量的owner成员为调用线程,设置互斥量的original_priority 成员为调用线程当前优先级,互斥量的hold成员加1

6)互斥量不可用,非阻塞调用。 设置调用线程错误码为-RT_ETIMEOUT,并返回-RT_ETIMEOUT;

7)互斥量不可用,阻塞调用。如果持有互斥量的线程优先级小于当前优先级,设置持有互斥量的线程优先级为调用线程优先级,以解决优先级反转的问题

8)挂起调用线程

9)设置调用线程定时器

10)调度

11)线程错误码等于-RT_EINTR则重新尝试获取互斥量

12)线程错误码不等于RT_EOK和-RT_EINTR,则直接返回错误码

13)调用钩子回调函数rt_object_take_hook

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    RT_DEBUG_IN_THREAD_CONTEXT; //断言不是在ISR中调用

    /* 参数检查 */
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
    RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象是互斥量

    thread = rt_thread_self(); //获取当前线程

    temp = rt_hw_interrupt_disable(); //关中断

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent))); //调用回调函数

    RT_DEBUG_LOG(RT_DEBUG_IPC,
                 ("mutex_take: current thread %s, mutex value: %d, hold: %d\n",
                  thread->name, mutex->value, mutex->hold)); //打印信息

    thread->error = RT_EOK; //将线程错误码设置为RT_EOK

    if (mutex->owner == thread) //线程已拥有互斥量
    {
        mutex->hold ++; //互斥量hold加1
    }
    else
    {
__again:

        if (mutex->value > 0) //互斥量值大于0
        {
            mutex->value --; //互斥量计数器减1

            mutex->owner             = thread; //设置互斥量owner调用线程
            mutex->original_priority = thread->current_priority; //保存线程当前优先级
            mutex->hold ++; //互斥量hold加1
        }
        else
        {
            if (time == 0) //不等待,返回超时
            {
                thread->error = -RT_ETIMEOUT; //设置错误为超时

                rt_hw_interrupt_enable(temp); //开中断

                return -RT_ETIMEOUT; //返回-RT_ETIMEOUT
            }
            else
            {
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
                                            thread->name)); //打印信息

                /* 如果持有互斥量的线程优先级小于当前优先级,设置持有互斥量的线程优先级为当前优先级,以解决优先级反转的问题 */
                if (thread->current_priority < mutex->owner->current_priority)
                {
                    rt_thread_control(mutex->owner,
                                      RT_THREAD_CTRL_CHANGE_PRIORITY,
                                      &thread->current_priority); //改变持有互斥量线程的优先级
                }

                rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
                                    thread,
                                    mutex->parent.parent.flag); //挂起当前线程

                if (time > 0) 
                {
                    RT_DEBUG_LOG(RT_DEBUG_IPC,
                                 ("mutex_take: start the timer of thread:%s\n",
                                  thread->name)); //打印

                    rt_timer_control(&(thread->thread_timer),
                                     RT_TIMER_CTRL_SET_TIME,
                                     &time); //设置线程定时器的超时时间
                    rt_timer_start(&(thread->thread_timer)); //重启线程定时器
                }

                rt_hw_interrupt_enable(temp); //开中断

                rt_schedule(); //调度

                if (thread->error != RT_EOK) //线程错误码不等于RT_EOK
                {
                    /* 被信号中断,重新尝试 */
                    if (thread->error == -RT_EINTR) goto __again; //goto到标签__again

                    return thread->error; //返回线程错误码
                }
                else
                {
                    temp = rt_hw_interrupt_disable(); //关中断
                }
            }
        }
    }

    rt_hw_interrupt_enable(temp); //开中断

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mutex->parent.parent))); //调用回调函数

    return RT_EOK; //返回RT_EOK
}

2.5.6、rt_mutex_release()函数

此函数将释放互斥锁,如果有线程挂起了互斥锁,它将被唤醒。

1)调用回调钩子函数rt_object_put_hook

2)调用线程不是互斥量持有线程,直接返回-RT_ERROR(互斥量只能被持有互斥量的线程释放!)

3)互斥量hold成员减1

4)如果mutex->hold为0,将持有该互斥量的线程的优先级恢复为原来的优先级 

5)如果有等待此互斥量的线程,唤醒第一个挂起的线程,并持有此互斥量。

6)如果需要调度,则进行调度

rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
    register rt_base_t temp;
    struct rt_thread *thread;
    rt_bool_t need_schedule;

    /* 参数检查 */
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
    RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量

    need_schedule = RT_FALSE; //设置need_schedule为RT_FALSE

    RT_DEBUG_IN_THREAD_CONTEXT; //上下文检查

    thread = rt_thread_self(); //获取当前线程

    temp = rt_hw_interrupt_disable(); //关中断

    RT_DEBUG_LOG(RT_DEBUG_IPC,
                 ("mutex_release:current thread %s, mutex value: %d, hold: %d\n",
                  thread->name, mutex->value, mutex->hold)); //打印信息

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent))); //调用回调函数

    /* 互斥量只能被持有互斥量的线程释放! */
    if (thread != mutex->owner) //调用此函数的线程不是持有互斥量的线程
    {
        thread->error = -RT_ERROR; //设置错误码为-RT_ERROR

        rt_hw_interrupt_enable(temp); //开中断

        return -RT_ERROR; //返回-RT_ERROR
    }

    mutex->hold --; //互斥量hold减1

    if (mutex->hold == 0) //互斥量hold等于0
    {
        /* 将持有该互斥量的线程的优先级恢复为原来的优先级 */
        if (mutex->original_priority != mutex->owner->current_priority)
        {
            rt_thread_control(mutex->owner,
                              RT_THREAD_CTRL_CHANGE_PRIORITY,
                              &(mutex->original_priority)); //改变回原来的优先级
        }

        /* 唤醒挂起的线程 */
        if (!rt_list_isempty(&mutex->parent.suspend_thread))
        {
            thread = rt_list_entry(mutex->parent.suspend_thread.next,
                                   struct rt_thread,
                                   tlist); //获取挂起线程

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s\n",
                                        thread->name)); //打印

            /* 设置新的持有线程和优先级 */
            mutex->owner             = thread; //设置持有线程为当前线程
            mutex->original_priority = thread->current_priority; //保存当前线程优先级
            mutex->hold ++; //hold加1

            rt_ipc_list_resume(&(mutex->parent.suspend_thread)); //唤醒线程

            need_schedule = RT_TRUE; //需要调度
        }
        else
        {
            mutex->value ++; //互斥量值加1

            mutex->owner             = RT_NULL; //设置互斥量owner为RT_NULL
            mutex->original_priority = 0xff; //设置互斥量original_priority设置为0xff
        }
    }

    rt_hw_interrupt_enable(temp); //开中断

    if (need_schedule == RT_TRUE) //是否需要调度
        rt_schedule(); //调度

    return RT_EOK; //返回RT_EOK
}

2.5.7、rt_mutex_control()函数

此函数可以获取或设置互斥对象的一些额外属性,当前没有功能。

rt_err_t rt_mutex_control(rt_mutex_t mutex, int cmd, void *arg)
{
    /* 参数检查 */
    RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
    RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量

    return -RT_ERROR; //返回-RT_ERROR
}

3、事件集

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

3.1、事件集机制

使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或 “逻辑或” 来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。

3.1.1、事件集特点

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

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

3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

3.2、事件集控制块

事件集控制块是操作系统用于管理事件的一个数据结构,由结构体 struct rt_event表示。

struct rt_event
{
    struct rt_ipc_object parent;                        /* ipc对象 */

    rt_uint32_t          set;                           /* 事件集 */
};
typedef struct rt_event *rt_event_t;

3.3、函数

3.3.1、rt_event_init()函数

此函数将初始化一个事件,并将其交由对象容器的管理。

rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)
{
	/* 参数检查 */
	RT_ASSERT(event != RT_NULL); //断言event != RT_NULL

	rt_object_init(&(event->parent.parent), RT_Object_Class_Event, name); //初始化事件集对象

	event->parent.parent.flag = flag; //设置事件集标志

	rt_ipc_object_init(&(event->parent)); //初始化事件集ipc对象

	event->set = 0; //初始化事件集

	return RT_EOK; //返回RT_EOK
}

3.3.2、rt_event_create()函数

此函数将从内存中分配一个事件对象。

rt_event_t rt_event_create(const char *name, rt_uint8_t flag)
{
    rt_event_t event;

    RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用

    event = (rt_event_t)rt_object_allocate(RT_Object_Class_Event, name); //分配事件集对象
    if (event == RT_NULL) //分配失败
        return event; //返回RT_NULL
 
    event->parent.parent.flag = flag; //设置事件集标志

    rt_ipc_object_init(&(event->parent)); //初始化事件集ipc对象

    event->set = 0; //初始化事件集

    return event; //返回event
}

3.3.3、rt_event_detach()函数

此函数将把事件对象从资源管理中分离出来。

rt_err_t rt_event_detach(rt_event_t event)
{
    /* 参数检查 */
    RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
    RT_ASSERT(rt_object_is_systemobject(&event->parent.parent)); //事件集对象是静态的

    rt_ipc_list_resume_all(&(event->parent.suspend_thread)); //唤醒因事件集而挂起的所有线程

    rt_object_detach(&(event->parent.parent)); //脱离事件集对象

    return RT_EOK; //返回RT_EOK
}

3.3.4、rt_event_delete()函数

此函数将删除一个事件对象并释放内存。

rt_err_t rt_event_delete(rt_event_t event)
{
    /* 参数检查 */
    RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集对象
    RT_ASSERT(rt_object_is_systemobject(&event->parent.parent) == RT_FALSE); //断言事件集对象不是静态的

    RT_DEBUG_NOT_IN_INTERRUPT; //断言不在ISR中调用

    rt_ipc_list_resume_all(&(event->parent.suspend_thread)); //唤醒因获取事件集而挂起的所有线程

    rt_object_delete(&(event->parent.parent)); //删除事件集对象

    return RT_EOK; //返回RT_EOK
}

3.3.5、rt_event_send()函数

发送事件函数可以发送事件集中的一个或多个事件。


rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
{
    struct rt_list_node *n;
    struct rt_thread *thread;
    register rt_ubase_t level;
    register rt_base_t status;
    rt_bool_t need_schedule;

    /* 参数检查 */
    RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集

    if (set == 0) //设置的事件为空
        return -RT_ERROR; //返回-RT_ERROR

    need_schedule = RT_FALSE; //设置need_schedule为RT_FALSE

    level = rt_hw_interrupt_disable(); //关中断

    event->set |= set; //设置事件

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(event->parent.parent))); //调用回调
    
    if (!rt_list_isempty(&event->parent.suspend_thread)) //是否有线程等待事件
    {
        n = event->parent.suspend_thread.next; //设置n等于event->parent.suspend_thread.next
        while (n != &(event->parent.suspend_thread)) //是否有因事件集而挂起的线程
        {
            thread = rt_list_entry(n, struct rt_thread, tlist); //获取线程

            status = -RT_ERROR; //设置status为-RT_ERROR
            if (thread->event_info & RT_EVENT_FLAG_AND) //事件集为与事件
            {
                if ((thread->event_set & event->set) == thread->event_set) //事件集全部满足条件
                {
                    status = RT_EOK; //设置status为RT_EOK
                }
            }
            else if (thread->event_info & RT_EVENT_FLAG_OR) //事件集为或事件
            {
                if (thread->event_set & event->set) //事件集部分满足条件
                {
                    /* 保存接收到的事件集 */
                    thread->event_set = thread->event_set & event->set; //保存满足事件集的条件

                    status = RT_EOK; //设置status为RT_EOK
                }
            }

            n = n->next; //设置n为n->next

            if (status == RT_EOK) //status等于RT_EOK
            {
                if (thread->event_info & RT_EVENT_FLAG_CLEAR) //需要清除事件
                    event->set &= ~thread->event_set; //清除事件

                rt_thread_resume(thread); //唤醒线程

                need_schedule = RT_TRUE; //需要调度
            }
        }
    }

    rt_hw_interrupt_enable(level); //开中断

    if (need_schedule == RT_TRUE) //是否需要调度
        rt_schedule(); //调度

    return RT_EOK; //返回RT_EOK
}

3.3.6、rt_event_recv()函数

此函数将从事件对象接收事件,如果事件不可用,线程将等待指定的时间。

rt_err_t rt_event_recv(rt_event_t   event,
                       rt_uint32_t  set,
                       rt_uint8_t   option,
                       rt_int32_t   timeout,
                       rt_uint32_t *recved)
{
    struct rt_thread *thread;
    register rt_ubase_t level;
    register rt_base_t status;

    RT_DEBUG_IN_THREAD_CONTEXT; //断言不在ISR中调用

    /* 参数检查 */
    RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集

    if (set == 0) //接收的事件集是否为空
        return -RT_ERROR; //返回-RT_ERROR

    status = -RT_ERROR; //初始化状态

    thread = rt_thread_self(); //获取当前线程

    thread->error = RT_EOK; //设置线程错误码为RT_EOK

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(event->parent.parent))); //调用回调函数

    level = rt_hw_interrupt_disable(); //关中断

    /* 检查事件 */
    if (option & RT_EVENT_FLAG_AND) //事件集为或事件
    {
        if ((event->set & set) == set) //事件集全部满足条件
            status = RT_EOK; //设置status为RT_EOK
    }
    else if (option & RT_EVENT_FLAG_OR) //事件集为与事件
    {
        if (event->set & set) //事件集部分满足条件
            status = RT_EOK; //设置status为RT_EOK
    }
    else
    {
        RT_ASSERT(0); //断言option参数错误
    }

    if (status == RT_EOK) //status是否等于RT_EOK
    {
        if (recved) //recved不为空
            *recved = (event->set & set); //保存接收到的事件

        if (option & RT_EVENT_FLAG_CLEAR) //是否需要清除事件
            event->set &= ~set; //清除事件
    }
    else if (timeout == 0) //超时时间为0
    {
        thread->error = -RT_ETIMEOUT; //设置线程错误码为-RT_ETIMEOUT
    }
    else
    {
        thread->event_set  = set; //设置线程事件集
        thread->event_info = option; //设置线程事件信息

        rt_ipc_list_suspend(&(event->parent.suspend_thread),
                            thread,
                            event->parent.parent.flag); //挂起线程

        /* 需要等待,启动线程定时器 */
        if (timeout > 0)
        {
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout); //设置线程定时器超时时间
            rt_timer_start(&(thread->thread_timer)); //启动线程定时器
        }

        rt_hw_interrupt_enable(level); //开中断

        rt_schedule(); //调度

        if (thread->error != RT_EOK) //线程错误码不等于RT_EOK
        {
            return thread->error; //返回线程错误码
        }

        level = rt_hw_interrupt_disable(); //关中断

        if (recved) //recved不为空
            *recved = thread->event_set; //保存接收到的事件
    }

    rt_hw_interrupt_enable(level); //开中断

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(event->parent.parent))); //调用回调函数

    return thread->error; //返回线程错误码
}

3.3.7、rt_event_control()函数

此函数可以获取或设置事件集对象的一些额外属性。

rt_err_t rt_event_control(rt_event_t event, int cmd, void *arg)
{
    rt_ubase_t level;

    /* 参数检查 */
    RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
    
    if (cmd == RT_IPC_CMD_RESET)  
    {
        level = rt_hw_interrupt_disable(); //关中断

        rt_ipc_list_resume_all(&event->parent.suspend_thread); //唤醒因获取事件集而挂起的所有线程

        event->set = 0; //初始化事件集

        rt_hw_interrupt_enable(level); //开中断

        rt_schedule(); //调度

        return RT_EOK; //返回RT_EOK
    }

    return -RT_ERROR; //返回-RT_ERROR
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内核7-线程间同步 的相关文章

  • 片内外设、片上外设和片外外设的区别

    片内外设就是片上外设 同一种意思不同说法而已 片内外设和片外外设的区别 片内 外设是两个概念 片内指做成芯片的集成电路内部 简称片内 片外同理显而易见 外设是外部设备的简称 是指集成电路芯片外部的设备 集成电路芯片与外部设备的连接一般需要专
  • Linux的IO端口和IO内存

    Linux的IO端口和IO内存 分类 linux编程 2011 01 14 13 22 866人阅读 评论 1 收藏 举报 io linux linux内核 struct 平台 x86 CPU对外设端口物理地址的编址方式有两种 一种是IO映
  • 树莓派内核编译

    一 概述 树莓派的github主页 https github com raspberrypi 里面包含了linux源码 交叉编译工具链等内容 对于我们要用到的有两个仓库 https github com raspberrypi linux
  • Linux mmap系统调用视角看缺页中断

    问题 1 mmap具体是怎么实现比read write少一次内存copy的 2 mmap共享映射和私有映射在内核实现的时候到底有什么区别 3 mmap的文件映射和匿名映射在内核实现的时候到底有什么区别 4 父子进程的COW具体怎么实现的 概
  • FreeRTOS-创建删除任务

    1 FreeRTOSConfig h文件 FreeRTOSConfig h配置文件作用 对FreeRTOS进行功能配置和裁剪 以及API函数的使能 相关的宏可以分为三大类 INCLUDE 配置FreeRTOS中可选的API函数 config
  • 【树莓派】Linux内核编译

    树莓派 Linux内核编译 树莓派的Linux内核编译有两种方法 一种是在树莓派上直接编译 另一种是利用交叉编译的方法 一般我们都推荐采用交叉编译的方式进行编译 这是因为通常交叉编译Pi内核的速度比Pi本身编译快得多 性能因素 下面就讲下如
  • qemu 启动自定义文件系统命令

    kvm qemu aarch64 bin qemu system aarch64 M virt smp 8 cpu cortex a76 m 4G nographic kernel out kernel arm64 Image append
  • 一文讲透!Windows内核 & x86中断机制详解

    搞内核研究的经常对中断这个概念肯定不陌生 经常我们会接触很多与中断相关的术语 按照软件和硬件进行分类 硬件CPU相关 IRQ IDT cli sti 软件操作系统相关 APC DPC IRQL 一直以来对中断这一部分内容弄的一知半解 操作系
  • jffs2:You cannot use older JFFS2 filesystems with newer kernels错误

    jffs2 You cannot use older JFFS2 filesystems with newer kernels错误 原因 可能在于交叉编译linux内核时 没有打开jffs2系统的使能开关 解决方法 在内核文件中执行make
  • RT-Thread记录(五、RT-Thread 临界区保护与FreeRTOS的比较)

    本文聊聊临界区 以及RT Thread对临界区的处理 通过源码分析一下 RT Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同 目录 前言 一 临界区 1 1 什么是临界区 1 2 RTOS中的临界区 二 RT Thre
  • late_initcall和module_init

    late initcall和module init 分类 linux驱动程序设计 2012 11 04 15 14 3680人阅读 评论 0 收藏 举报 所有的 init函数在区段 initcall init中还保存了一份函数指针 在初始化
  • 自己动手写RTOS:02-在M3内核上实现pendsvc

    自己动手写RTOS 自己动手写RTOS 01基础知识和理论部分 自己动手写RTOS 02 在M3内核上实现pendsvc 文章目录 自己动手写RTOS 一 M3内核的相关知识 1 1寄存器 1 2特殊寄存器 1 3堆栈 二 pendSVC实
  • 字符设备驱动相关函数

    Linux内核中 a 使用cdev结构体来描述字符设备 b 通过其成员dev t来定义设备号 分为主 次设备号 以确定字符设备的唯一性 c 通过其成员file operations来定义字符设备驱动提供给VFS的接口函数 如常见的open
  • 基于树莓派博通BCM2835芯片手册导读写编简单引脚驱动代码编译和测试(树莓派)

    编写引脚驱动代码 这边写的是17引脚的驱动代码代码 IO口控制的代码在下面 这边只是简单的代码 驱动代码 include
  • 多线程系列之——事件内核对象

    所有内核对象里面事件内核对象是最简单的一个 它包括一个使用计数 还有两个布尔值 一个布尔值用来表示事件是手动重置事件还是自动重置事件 另一个布尔值表示当前是否处于触发状态 当一个手动重置事件被触发的时候 所有等待该事件的线程都能变成调度状态
  • CentOS7编译内核

    下面记录了我在CentOS7上编译新内核的过程 背景 实验室的一台服务器上装且仅装了CentOS7 内核版本为3 10 0 327 el7 x86 64 我要在当前系统上 编译 安装内核4 1 16 搭建编译环境 sudo yum inst
  • uCOSii中的互斥信号量

    uCOSii中的互斥信号量 一 互斥型信号量项管理 MUTUAL EXCLUSION SEMAPHORE MANAGEMENT OSMutexAccept 无条件等待地获取互斥型信号量 OSMutexCreate 建立并初始化一个互斥型信号
  • 在 Windows 上对嵌入式软件进行原型设计和仿真

    我正在寻找用于原型设计 虚拟原型设计 模拟和测试桌面 Windows 上深度嵌入式 C 代码的工具和技术 包括构建由按钮 LED 和 LCD 显示器 分段和图形 组成的真实嵌入式前面板 我对可能的低级方法特别感兴趣 使用纯 C 代码和原始
  • 像 PTLsim 这样的 CAS 模拟器如何实现 x86 硬件的周期精确模拟?

    谁能告诉我 CAS 软件怎么样http www ptlsim org 工作 如果不知道每条指令使用了多少个周期 也不知道 CPU 分支预测逻辑 那么它们如何实现周期精度 或者一切都可以通过保密协议获得吗 我想它们可能可以非常准确地命中或错过
  • 中断管理学习

    中断管理 什么是中断 简单的解释就是系统正在处理某一个正常事件 忽然被另一个需要马上处理的紧急事件打断 系统转而处理这个紧急事件 待处理完毕 再恢复运行刚才被打断的事件 生活中 我们经常会遇到这样的场景 当你正在专心看书的时候 忽然来了一个

随机推荐

  • LaTex学习笔记(三):矩阵的输入

    矩阵的输入类似于表格 在latex中输入矩阵有多种方式 1 left begin array clr 4343 434 235 45 3232 34 56 232 3467 end array right 2 begin bmatrix 不
  • Excel 两列数据中相同的数据进行同行显示

    一 要求 假设您有两个列 分别是A列和B列 需要在C列中找出A列对应的B列的值 二 方案 方法1 寻常思路 凸显重复项 对A列单独进行筛选 按颜色进行排序 然后升序 对B列重复上述操作即可 方法2 两个公式 VLOOKUP 纵向查找函数 语
  • HDFS操作

    1 使用oiv命令查看hadoop 的镜像文件 hadoop s201 hadoop dfs name current hdfs oiv Usage bin hdfs oiv OPTIONS i INPUTFILE o OUTPUTFILE
  • Python处理缺失数据

    目录 1 缺失原因 2 缺失类型 3 处理方法 3 1 删除 3 1 1 统计每列缺失值的个数 3 1 2 直接删除含有缺失值的行 3 1 3 直接删除含有缺失值的列 3 1 4 只删除全是缺失值的行 3 1 5 保留至少有4个非缺失值的行
  • 51单片机(STC)串口无阻塞发送函数

    目录 一 简介 1 1 开发环境 1 2 功能描述 二 串口程序 2 1 串口配置 2 2 变量定义 2 3 中断函数 2 4 发送函数 一 简介 1 1 开发环境 KeilC51 单片机型号STC15F2K60S2 1 2 功能描述 使用
  • Hutool导出Excel,导多个Sheet页

    重要方法 指定要写出的 Sheet 页 bigWriter setSheet sheet getSheetName 工具类 public class HuExcelUtils 导出多个 Sheet 页 param response para
  • 零售业未来如何破局?抓住数智化经营的两把利刃!

    导语 数字化转型浪潮席卷了千行百业 有人从中看出了汹涌的挑战 也有人从中嗅出了美妙的商机 对于零售企业而言 当前数智经营进入了哪个阶段 未来的破局之道又在何方 我们邀请到了广东省 CIO 协会消费品与零售行业分会会长 腾讯云 TVP 行业大
  • Unity3D

    Cheer Up 游戏说明 除了音效 游戏地图上的元素有 草丛 玩家可以躲进去 敌人攻击不到 河流 双方都过不去 但是子弹可以穿过 铁墙 坦克和子弹都过不去 砖墙 一发子弹摧毁后坦克可以过去 空气墙 围在地图周围 防止出界 敌方大坦克 打两
  • 介绍几款Python科学计算发行版

    目前比较流行的Python科学计算发行版 主要有这么几个 Python x y GUI基于PyQt 曾经是功能最全也是最强大的 而且是Windows系统中科学免费Python发行版的不二选择 不过今时已不同往昔 PythonXY里面的许多包
  • 在Excel中使用SQL

    说明 Excel中许多函数虽然能代替SQL的功能 但是比起SQL 还是有一些逊色 特意做了这个教程 主要有 分组统计 Excel中用数据透视表 SQL中用Group By 去重 Excel中可以用条件标识功能 开始 gt 条件标识 SQL中
  • 无闪视频风格切换新思路

    近段时间 视频风格切换应用的热度逐渐上升 包括已经成熟应用的gen 还有Ebsynth等 但是这些视频的切换都有一个通病就是视频会出现闪烁 导致最终的切换效果不佳 最近 有开源项目CoDeF提供了一种新的思路来解决这种闪烁的问题 从已经的公
  • ubuntu下使用Eclipse搭建Hadoop开发环境

    在ubuntu下使用Eclipse搭建Hadoop开发环境 一 安装准备 1 JDK版本 jdk1 7 0 jdk 7 linux i586 tar gz 2 hadoop版本 hadoop 1 1 1 hadoop 1 1 1 tar g
  • Milvus2.0

    一 介绍 项目官网 Milvus Open Source Vector Database built for scalable similarity searchhttps milvus io cn 项目文档 关于 Milvus Milvu
  • 页面禁止鼠标右键点击

    把这段代码放到head里就OK了
  • ChatGPT+Midjourney可量产“宫崎骏”,AI将会让多少设计师失业?

    最近 大家都被横空出世的ChatGPT惊艳到了 瞬间在全世界爆红的ChatGPT 除了陪聊 它还能写论文 写小说 写代码 编剧本 几乎无所不能 ChatGPT让科技巨头谷歌发出了红色警报 一夜之间全世界的打工人们也都慌了 我们的很多工作似乎
  • 带环单链表+带环单链表

    带环单链表 带环单链表 判断相交 若共用一个环则相交 若不共用则不相交 求解相交点 判断是否带环 定义快慢指针 快指针一次走两步 慢指针一次走一步 若快指针走完 慢指针走到一半 奇数为中间的节点 偶数为中间的两个中的后一个节点 则无环 若有
  • Windows下非常好用的包管理器scoop介绍

    以前我写过文章介绍过Windows下的包管理器Chocolatey 而Chocolatey并不是唯一的选择 还有一个很流行的选择就是scoop 原来我用Chocolatey比较多一点 但是后来因为我发现Chocolatey安装的JDK等软件
  • 700亿参数Llama 2训练加速195%!数据成为其提升效果的关键要素

    Llama 2是Meta AI正式发布的最新一代开源大模型 达到了2万亿的token 精调Chat模型是在100万人类标注数据上训练 Llama 2在包括推理 编码 精通性和知识测试等许多外部基准测试中都优于其他开源语言模型 Llama 2
  • springboot中logback配置conversionRule、ConsoleAppender

    1 概述 在Java生态中 日志框架有很多 常见的有 jul JDK提供 log4j logback等 随着各种日志框架的出现 在实际项目中使用方式 暴露的日志api 依赖等问题都需要程序员考虑和选择 面对中场景SLF4J出现了即简单日志门
  • 内核7-线程间同步

    目录 1 信号量 1 1 信号量机制 1 2 信号量的使用场合 1 2 1 线程同步 1 2 2 锁 1 2 3 中断与线程的同步 1 2 4 资源计数 1 3 信号量控制块 1 4 函数 1 4 1 rt sem init 函数 1 4