互斥锁

互斥锁又叫相互排斥的信号量,是一种特殊的二值信号量。互斥锁用来保证共享资源的完整性,保证在任一时刻,只能有一个线程访问该共享资源,线程要访问共享资源,必须先拿到互斥锁,访问完成后需要释放互斥锁。嵌入式的共享资源包括内存、IO、SCI、SPI等,如果两个线程同时访问共享资源可能会出现问题,因为一个线程可能在另一个线程修改共享资源的过程中使用了该资源,并认为共享资源没有变化。

互斥锁的操作只有两种上锁或解锁,同一时刻只会有一个线程持有某个互斥锁。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行解锁或持有它。

对互斥锁的主要操作包括:调用pthread_mutex_init()初始化一个互斥锁,调用 pthread_mutex_destroy()销毁互斥锁,调用pthread_mutex_lock()对互斥锁上锁,调用 pthread_mutex_lock()对互斥锁解锁。

使用互斥锁会导致一个潜在问题是线程优先级翻转。在RT-Thread操作系统中实现的是优先级继承算法。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

有关优先级反转的详细信息请参考RT-Thread编程手册任务间同步及通信一章互斥量一节。请点击链接

互斥锁控制块

每个互斥锁对应一个互斥锁控制块,包含对互斥锁进行的控制的一些信息。创建互斥锁前必须先定义一个pthread_mutex_t类型的变量,pthread_mutex_t是pthread_mutex的重定义,pthread_mutex数据结构定义在pthread.h头文件里,数据结构如下:

  1. struct pthread_mutex
  2. {
  3. pthread_mutexattr_t attr; /* 互斥锁属性 */
  4. struct rt_mutex lock; /* RT-Thread互斥锁控制块 */
  5. };
  6. typedef struct pthread_mutex pthread_mutex_t;
  7.  
  8. rt_mutexRT-Thread 内核里定义的一个数据结构,定义在rtdef.h头文件里,数据结构如下:
  9.  
  10. struct rt_mutex
  11. {
  12. struct rt_ipc_object parent; /* 继承自ipc_object类 */
  13. rt_uint16_t value; /* 互斥锁的值 */
  14. rt_uint8_t original_priority; /* 持有线程的原始优先级 */
  15. rt_uint8_t hold; /* 互斥锁持有计数 */
  16. struct rt_thread *owner; /* 当前拥有互斥锁的线程 */
  17. };
  18. typedef struct rt_mutex* rt_mutex_t; /* rt_mutext_t为指向互斥锁结构体的指针 */

互斥锁初始化

函数原型

  1. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

  1. 参数 描述

  1. mutex 互斥锁句柄,不能为NULL
  2.  
  3. attr 指向互斥锁属性的指针,若该指针NULL,则使用默认的属性。

函数返回

初始化成功返回0,参数无效返回EINVAL。

此函数会初始化mutex互斥锁,并根据attr指向的互斥锁属性对象设置mutex属性,成功初始化后互斥锁处于未上锁状态,线程可以获取,此函数是对rt_mutex_init()函数的封装。

除了调用pthread_mutex_init()函数创建一个互斥锁,还可以用宏 PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(结构体常量),等同于调用pthread_mutex_init()时attr指定为NULL。

关于互斥锁属性及相关函数会在线程高级编程一章里有详细介绍,一般情况下采用默认属性就可以。

销毁互斥锁

函数原型

  1. int pthread_mutex_destroy(pthread_mutex_t *mutex);

  1. 参数 描述

  1. mutex 互斥锁句柄,不能为NULL

函数返回

销毁成功返回0,mutex为空或者mutex已经被销毁过返回EINVAL,互斥锁正在被使用返回EBUSY。

此函数会销毁mutex互斥锁。销毁后互斥锁mutex处于未初始化状态。销毁以后互斥锁的属性和控制块参数将不在有效,但可以调用pthread_mutex_init()对销毁后的互斥锁重新初始化。但不需要销毁使用宏PTHREAD_MUTEX_INITIALIZER静态初始化的互斥锁。

当确定互斥锁没有被锁住,且没有线程阻塞在该互斥锁上,才可以销毁该互斥锁。

阻塞方式对互斥锁上锁

函数原型

  1. int pthread_mutex_lock(pthread_mutex_t *mutex);

  1. 参数 描述

  1. mutex 互斥锁句柄,不能为NULL

函数返回

成功上锁返回0,参数无效返回EINVAL,互斥锁mutex不为嵌套锁的情况下线程重复调用返回EDEADLK。此函数对mutex互斥锁上锁,此函数是对rt_mutex_take()函数的封装。如果互斥锁mutex还没有被上锁,那么申请该互斥锁的线程将成功对该互斥锁上锁。如果互斥锁mutex已经被当前线程上锁,且互斥锁类型为嵌套锁,则该互斥锁的持有计数加1,当前线程也不会挂起等待(死锁),但线程必须对应相同次数的解锁。如果互斥锁mutex被其他线程上锁持有,则当前线程将被阻塞,一直到其他线程对该互斥锁解锁后,等待该互斥锁的线程将按照先进先出的原则获取互斥锁。

非阻塞方式对互斥锁上锁

函数原型

  1. int pthread_mutex_trylock(pthread_mutex_t *mutex);

  1. 参数 描述

  1. mutex 互斥锁句柄,不能为NULL

函数返回

成功上锁返回0,参数无效返回EINVAL,互斥锁mutex不为嵌套锁的情况下线程重复调用返回EDEADLK,互斥锁mutex已经被其他线程上锁返回EBUSY。

此函数是pthread_mutex_lock()函数的非阻塞版本。区别在于如果互斥锁mutex已经被上锁,线程不会被阻塞,而是马上返回错误码。

互斥锁解锁

函数原型

  1. int pthread_mutex_unlock(pthread_mutex_t *mutex);

  1. 参数 描述

  1. mutex 互斥锁句柄,不能为NULL

函数返回

成功上锁返回0,参数无效返回EINVAL,解锁其他线程持有的类型为检错锁的互斥锁mutex返回EPERM。

调用此函数给mutex互斥锁解锁,是对rt_mutex_release()函数的封装。当线程完成共享资源的访问后,应尽快释放占有的互斥锁,使得其他线程能及时获取该互斥锁。只有已经拥有互斥锁的线程才能释放它,每释放一次该互斥锁,它的持有计数就减1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),互斥锁才变为可用,等待在该互斥锁上的线程将按先进先出方式被唤醒。如果线程的运行优先级被互斥锁提升,那么当互斥锁被释放后,线程恢复为持有互斥锁前的优先级。

互斥锁示例代码

这个程序会初始化2个线程,它们拥有相同的优先级,2个线程都会调用同一个printer()函数输出自己的字符串,printer()函数每次只输出一个字符,之后休眠1秒,调用printer()函数的线程同样也休眠。如果不使用互斥锁,线程1打印了一个字符,休眠后执行线程2,线程2打印一个字符,这样就不能完整的打印线程1和线程2的字符串,打印出的字符串是混乱的。如果使用了互斥锁保护2个线程共享的打印函数printer(),线程1拿到互斥锁后执行printer()打印函数打印一个字符,之后休眠1秒,这是切换到线程2,因为互斥锁已经被线程1上锁,线程2将阻塞,直到线程1的字符串打印完整后主动释放互斥锁后线程2才会被唤醒。

  1. #include <pthread.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4.  
  5. /* 线程控制块 */
  6. static pthread_t tid1;
  7. static pthread_t tid2;
  8. /* 互斥锁控制块 */
  9. static pthread_mutex_t mutex;
  10. /* 线程共享的打印函数 */
  11. static void printer(char* str)
  12. {
  13. while(*str != 0)
  14. {
  15. putchar(*str); /* 输出一个字符 */
  16. str++;
  17. sleep(1); /* 休眠1秒 */
  18. }
  19. printf("\n");
  20. }
  21. /* 函数返回值检查 */
  22. static void check_result(char* str,int result)
  23. {
  24. if (0 == result)
  25. {
  26. printf("%s successfully!\n",str);
  27. }
  28. else
  29. {
  30. printf("%s failed! error code is %d\n",str,result);
  31. }
  32. }
  33. /*线程入口*/
  34. static void* thread1_entry(void* parameter)
  35. {
  36. char* str = "thread1 hello RT-Thread";
  37. while (1)
  38. {
  39. pthread_mutex_lock(&mutex); /* 互斥锁上锁 */
  40.  
  41. printer(str); /* 访问共享打印函数 */
  42.  
  43. pthread_mutex_unlock(&mutex); /* 访问完成后解锁 */
  44.  
  45. sleep(2); /* 休眠2秒 */
  46. }
  47. }
  48. static void* thread2_entry(void* parameter)
  49. {
  50. char* str = "thread2 hi world";
  51. while (1)
  52. {
  53. pthread_mutex_lock(&mutex); /* 互斥锁上锁 */
  54.  
  55. printer(str); /* 访问共享打印函数 */
  56.  
  57. pthread_mutex_unlock(&mutex); /* 访问完成后解锁 */
  58.  
  59. sleep(2); /* 休眠2秒 */
  60. }
  61. }
  62. /* 用户应用入口 */
  63. int rt_application_init()
  64. {
  65. int result;
  66. /* 初始化一个互斥锁 */
  67. pthread_mutex_init(&mutex,NULL);
  68.  
  69. /*创建线程1,线程入口是thread1_entry, 属性参数为NULL选择默认值,入口参数是NULL*/
  70. result = pthread_create(&tid1,NULL,thread1_entry,NULL);
  71. check_result("thread1 created",result);
  72.  
  73. /*创建线程2,线程入口是thread2_entry, 属性参数为NULL选择默认值,入口参数是NULL*/
  74. result = pthread_create(&tid2,NULL,thread2_entry,NULL);
  75. check_result("thread2 created",result);
  76.  
  77. return 0;
  78. }