LV05-03-线程同步-03-自旋锁
本文主要是线程同步——自旋锁的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、自旋锁的概念
1. 基本概念与特性
自旋锁与互斥锁很相似,从本质上说也是一把锁,在访问共享资源之前对自旋锁进行上锁,在访问完成后释放自旋锁(解锁);事实上,从实现方式上来说,互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层。
如果在获取自旋锁时,自旋锁处于未锁定状态,那么将立即获得锁(对自旋锁上锁);如果在获取自旋锁时,自旋锁已经处于锁定状态了,那么获取锁操作将会在原地“自旋”,直到该自旋锁的持有者释放了锁。这与互斥锁很相似,都是为了保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。但是互斥锁在无法获取到锁时会让线程陷入阻塞等待状态;而自旋锁在无法获取到锁时,将会在原地“自旋”等待。“自旋”其实就是调用者一直在循环查看该自旋锁的持有者是否已经释放了锁,“自旋”一词因此得名。
自旋锁的不足之处在于:自旋锁一直占用的CPU
,它在未获得锁的情况下,一直处于运行状态(自旋),所以占着CPU
,如果不能在很短的时间内获取锁,这会使CPU
效率降低。
2. 应用场景
自旋锁通常用于以下情况:需要保护的代码段执行时间很短,这样就会使得持有锁的线程会很快释放锁,而“自旋”等待的线程也只需等待很短的时间;在这种情况下就比较适合使用自旋锁,效率高!
3. 与互斥锁的比较
- 实现方式上的区别
互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
- 开销上的区别
获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得CPU
使用效率降低,故自旋锁不适用于等待时间比较长的情况。
- 使用场景的区别
自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU
使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁。
【注意事项】试图对同一自旋锁加锁两次必然会导致死锁,而试图对同一互斥锁加锁两次不一定会导致死锁,原因在于互斥锁有不同的类型,当设置为PTHREAD_MUTEX_ERRORCHECK
类型时,会进行错误检查,第二次加锁会返回错误,所以不会进入死锁状态。
二、基本操作
1. 自旋锁初始化
1.1 pthread_spin_init()
1.1.1 函数说明
在linux
下可以使用man pthread_spin_init
命令查看该函数的帮助手册。
1 | /* Compile and link with -pthread. */ |
【函数说明】该函数初始化一个自旋锁。
【函数参数】
lock
:pthread_spinlock_t *
类型,指向需要进行初始化操作的自旋锁对象。pshared
:int
类型,表示自旋锁的进程共享属性。
点击查看 pshared 可以取的值
PTHREAD_PROCESS_SHARED | 共享自旋锁。该自旋锁可以在多个进程中的线程之间共享 |
PTHREAD_PROCESS_PRIVATE | 私有自旋锁。只有本进程内的线程才能够使用该自旋锁 |
【返回值】int
类型,成功返回0
;失败将返回一个非0
的错误码。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
1.1.2 使用实例
暂无。
2. 加锁和解锁
2.1 pthread_spin_lock()
2.1.1 函数说明
在linux
下可以使用man pthread_spin_lock
命令查看该函数的帮助手册。
1 | /* Compile and link with -pthread. */ |
【函数说明】该函数将对自旋锁加锁,获取自旋锁。
【函数参数】
lock
:pthread_spinlock_t *
类型,指向已经初始化的自旋锁对象。
【返回值】int
类型,成功返回0
;失败将返回一个非0
的错误码。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
2.1.2 使用实例
暂无。
2.2 pthread_spin_trylock()
2.2.1 函数说明
在linux
下可以使用man pthread_spin_trylock
命令查看该函数的帮助手册。
1 | /* Compile and link with -pthread. */ |
【函数说明】该函数将对自旋锁加锁,获取自旋锁。类似于pthread_spin_lock()
,不同之处在于,如果lock
引用的自旋锁当前处于锁定状态,那么调用将立即返回错误EBUSY
,而不是自旋。
【函数参数】
lock
:pthread_spinlock_t *
类型,指向已经初始化过的自旋锁对象。
【返回值】int
类型,成功返回0
;失败将返回一个非0
的错误码。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
2.2.2 使用实例
暂无。
2.3 pthread_spin_unlock()
2.3.1 函数说明
在linux
下可以使用man pthread_spin_unlock
命令查看该函数的帮助手册。
1 | /* Compile and link with -pthread. */ |
【函数说明】该函数用于对自旋锁解锁、释放自旋锁。
【函数参数】
lock
:pthread_spinlock_t *
类型,指向已经初始化过的自旋锁对象。
【返回值】int
类型,成功返回0
;失败将返回一个非0
的错误码。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
2.3.2 使用实例
暂无。
3. 自旋锁销毁
3.1 pthread_spin_destroy()
3.1.1 函数说明
在linux
下可以使用man pthread_spin_destroy
命令查看该函数的帮助手册。
1 | /* Compile and link with -pthread. */ |
【函数说明】该函数销毁一个不再使用的自旋锁。
【函数参数】
lock
:pthread_spinlock_t *
类型,指向需要进行销毁的自旋锁对象。
【返回值】int
类型,成功返回0
;失败将返回一个非0
的错误码。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
3.1.2 使用实例
暂无。
4. 自旋锁使用实例
点击查看实例
1 |
|
程序中的宏macro_spin
用于决定是否使用自旋锁。
- 若
macro_spin
为0
,则不使用自旋锁。 - 若
macro_spin
为1
,则使用自旋锁。
在终端执行以下命令编译程序:
1 | gcc test.c -Wall -l pthread # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | This is threadMutex1 thread create,ret=0,tid1=140129428600384 |
然后我们打开test.txt
文件,查看文件内容如下:
1 | II wrwriittee tthhrreeaaddMutMuext2ex l1 inlei |
该文件被两个线程共享,两个线程都是写入,未保护共享文件,导致文件写入出错。
在终端执行以下命令编译程序:
1 | gcc test.c -Wall -l pthread # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | This is threadMutex1 thread create,ret=0,tid1=140667718608448 |
然后我们打开test.txt
文件,查看文件内容如下:
1 | I write threadMutex1 line |
该文件被两个线程共享,两个线程都是写入,使用自旋锁保护共享文件,文件写入正常。