这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » 嵌入式Linux:线程同步(自旋锁)

共1条 1/1 1 跳转至

嵌入式Linux:线程同步(自旋锁)

菜鸟
2025-03-19 10:25:37     打赏

Linux 自旋锁(spinlock)是一种用于保护共享资源的锁机制,常用于多核处理器中,在某个核(或线程)试图获取锁时,如果发现锁已被其他核持有,它会忙等(不断循环检查)而不是让出 CPU 时间片。

自旋锁的特点是适用于锁的持有时间非常短的场景,因为它在等待期间不会主动放弃 CPU,而是不断尝试获取锁,这在多核系统中可以避免由于线程调度带来的上下文切换开销。

工作原理:

加锁线程尝试获取锁,如果成功,则进入临界区。如果锁已经被占用,线程会不停地轮询检查锁是否释放。忙等(自旋)如果锁被占用,线程会持续忙等,不会主动让出 CPU。这样避免了上下文切换,但消耗了 CPU 资源,因此自旋锁适用于锁定时间较短的场景。解锁当临界区的任务完成后,线程释放锁,其他正在忙等的线程可以继续尝试获取锁。

自旋锁常用于以下情况:

需要保护的代码段执行时间非常短,能够迅速释放锁。
不希望线程进入睡眠状态或导致上下文切换,尤其是在内核中的中断处理程序或者性能要求高的系统。
多核系统中,并发访问的共享资源保护,避免线程在上下文切换中浪费时间。

自旋锁与互斥锁的比较如下:

实现方式上的区别

自旋锁是一种轻量级锁机制,它本质上是在忙等状态下获取锁。当线程无法获取锁时,线程不会进入睡眠或等待状态,而是会不断地检查锁的状态,直到可以成功获取锁。互斥锁则是一种更高层的锁,通常在无法获取锁时会导致线程进入阻塞状态。互斥锁可以让操作系统将当前线程挂起,等待锁可用时再唤醒。

开销上的区别

自旋锁的主要优势在于没有上下文切换的开销,特别适用于锁持有时间很短的场景。由于自旋锁不会导致线程休眠,所以在处理器繁忙时可能会浪费 CPU 时间。如果等待时间较长,忙等会消耗过多的 CPU 资源,反而导致效率下降。互斥锁的开销较大,因为线程在获取不到锁时会陷入休眠,直到获取到锁时才会被唤醒。休眠和唤醒的代价很高,特别是在频繁锁定/解锁的场景中会影响性能。

适用场景上的区别

自旋锁常用于内核中或者需要避免上下文切换的场景。特别是在中断上下文中,自旋锁更为合适,因为中断处理程序不能被阻塞或休眠。它适用于那些执行时间极短的临界区,锁的持有时间必须足够短,才能避免因自旋导致 CPU 资源浪费。互斥锁更适用于用户态的程序或者锁定时间较长的临界区。当程序无法获取到互斥锁时,系统可以调度其他线程运行,直到锁被释放,适合长时间的等待操作。

死锁问题

如果对同一个自旋锁进行两次加锁操作,必然会导致死锁,因为自旋锁不具备递归性。
互斥锁则可以通过特定的类型来避免死锁。例如 PTHREAD_MUTEX_ERRORCHECK 类型的互斥锁在检测到重复加锁时,会返回错误而不是陷入死锁状态。

1、自旋锁初始化与销毁

自旋锁需要在使用前进行初始化,并在不再使用时销毁。

初始化自旋锁函数如下:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

参数:

lock:指向需要初始化的自旋锁对象。
pshared:自旋锁的共享属性,可以取值:
PTHREAD_PROCESS_SHARED:允许在多个进程中的线程之间共享自旋锁。
PTHREAD_PROCESS_PRIVATE:自旋锁只能在同一进程内的线程之间使用。

返回值:成功时返回 0,失败时返回非零错误码。

销毁自旋锁函数如下:

int pthread_spin_destroy(pthread_spinlock_t *lock);

参数:lock:指向要销毁的自旋锁对象。

返回值:成功时返回 0,失败时返回非零错误码。


2、自旋锁加锁与解锁

加锁函数如下:

int pthread_spin_lock(pthread_spinlock_t *lock);

参数:lock:指向要加锁的自旋锁对象。

返回值:成功时返回 0;如果锁已经被其他线程占用,则线程会忙等,直到成功获取锁,最终返回 0。

尝试加锁函数如下:

int pthread_spin_trylock(pthread_spinlock_t *lock);

参数:lock:指向要加锁的自旋锁对象。

返回值

成功时返回 0。
如果锁已被占用,立即返回 EBUSY。

解锁函数如下:

int pthread_spin_unlock(pthread_spinlock_t *lock);

参数:lock:指向要解锁的自旋锁对象。

返回值:成功时返回 0,失败时返回非零错误码。

下面是一个完整的示例,展示如何使用自旋锁,包括初始化、加锁、解锁和销毁:

pthread_spinlock_t spinlock; // 定义自旋锁
int shared_data = 0; // 共享数据
void *thread_func(void *arg) {
    pthread_spin_lock(&spinlock); // 加锁
    shared_data++;
    printf("Thread %ld: shared_data = %dn", (long)arg, shared_data);
    pthread_spin_unlock(&spinlock); // 解锁
    return NULL;
}
int main() {
    pthread_t threads[2];
    // 初始化自旋锁
    if (pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE) != 0) {
        perror("Failed to initialize spinlock");
        return 1;
    }
    // 创建两个线程
    pthread_create(&threads[0], NULL, thread_func, (void *)1);
    pthread_create(&threads[1], NULL, thread_func, (void *)2);
    // 等待线程结束
    pthread_join(threads[0], NULL);
    pthread_join(threads[1], NULL);
    // 销毁自旋锁
    if (pthread_spin_destroy(&spinlock) != 0) {
        perror("Failed to destroy spinlock");
        return 1;
    }
    return 0;
}

自旋锁的主要问题在于,它在获取不到锁时不会释放 CPU,而是持续消耗资源。

如果锁持有时间较长,CPU 的利用效率会急剧下降。

因此,自旋锁不适合用于长时间锁定的场景,只适合那些临界区极短的操作。

通过对 pthread_spin_* 函数的合理使用,可以有效管理多线程访问共享资源的同步问题。

确保在适当的地方进行加锁和解锁,以防止死锁和资源竞争。





关键词: 嵌入式     Linux     线程     pthread    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
站长统计
×

有奖问卷
英飞凌400V CoolSiC™重新定义高性能电源设计的功率密度和效率,含有奖小问卷,限前200名工程师!