文档章节

Linux多线程——使用互斥量同步线程

 猫改不了吃鱼
发布于 2017/05/15 15:27
字数 1912
阅读 5
收藏 0

一、什么是互斥量

 

互斥量是另一种用于多线程中的同步访问方法,它允许程序锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁。

 

二、互斥量的函数的使用

 

它们的定义与使用信号量的函数非常相似,它们的定义如下:

  1. #include <pthread.h>  
  2. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);  
  3.   
  4. int pthread_mutex_lock(pthread_mutex_t *mutex);  
  5.   
  6. int pthread_mutex_unlock(pthread_mutex_t *mutex);  
  7.   
  8. int pthread_mutex_destroy(pthread_mutex_t *mutex);  

它们的意义就如它们的名字所示的那样,成功时返回0,失败时返回错误代码,它们并不设置errno。

 

pthread_mutex_init函数中的参数mutexattr指定互斥量的属性,在这里我们并不关心互斥量的属性,所以把它设置为NULL,使用默认属性即可。同样的,pthread_mutex_lock和pthread_mutex_unlock都是原子操作,如果一个线程调用pthread_mutex_lock试图锁住互斥量,而该互斥量,又被其他线程锁住(占用),则该线程的pthread_mutex_lock调用就会阻塞,直到其他线程对该互斥量进行解锁,该线程才能获得该互斥量,pthread_mutex_lock调用才会返回。

 

注意,使用互斥量的默认属性,如果程序试图对一个已经加锁的互斥量调用pthread_mutex_lock,程序就会阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以这个互斥量就永远不会被解锁,也就是说,程序就会进入死锁的状态。在使用时要多加注意,确保在同一个线程中,对加锁的互斥再次进行加锁前要对其进行解锁。

 

三、使用互斥量进行线程同步

 

下面以一个简单的多线程程序来演示如何使用互斥量来进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程调用函数pthread_mutex_lock对互斥量加锁,等待输入,输入完成后,调用函数pthread_mutex_unlock对互斥量解锁,从而使线程函数中的对互斥量加锁的pthread_mutex_lock函数返回并执行子线程中的代码。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它调用pthread_mutex_unlock对互斥量解锁,使主线程能够继续获得互斥量(即对其加锁函数返回),再次执行输入功能直到主线程再次调用pthread_mutex_unlock对其解锁,一直如此重复,直到输入end。

 

源文件为lockthread.c,源代码如下:

  1. #include <unistd.h>  
  2. #include <pthread.h>  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. #include <string.h>  
  6.   
  7.   
  8. //声明线程函数和互斥量  
  9. void* thread_func(void *msg);  
  10. pthread_mutex_t mutex;  
  11.   
  12.   
  13. #define MSG_SIZE 512  
  14.   
  15.   
  16. int main()  
  17. {  
  18.     int res = -1;  
  19.     pthread_t thread;  
  20.     void *thread_result = NULL;  
  21.     char msg[MSG_SIZE] = {'\0'};  
  22.     //初始化互斥量,使用默认的互斥量属性  
  23.     res = pthread_mutex_init(&mutex, NULL);  
  24.     if(res != 0)  
  25.     {  
  26.         perror("pthread_mutex_init failed\n");  
  27.         exit(EXIT_FAILURE);  
  28.     }  
  29.     //创建子线程,并把msg作为线程函数的参数传递给thread_func  
  30.     res = pthread_create(&thread, NULL, thread_func, msg);  
  31.     if(res != 0)  
  32.     {  
  33.         perror("pthread_create failed\n");  
  34.         exit(EXIT_FAILURE);  
  35.     }  
  36.     //输入字符串,以串‘end’结束  
  37.     printf("Input some test. Enter 'end' to finish\n");  
  38.     //把互斥量mutex加锁,以确保同一时间只有该线程可以访问msg中的数据  
  39.     pthread_mutex_lock(&mutex);  
  40.     while(strcmp("end\n", msg) != 0)  
  41.     {  
  42.         if(strncmp("TEST", msg, 4) == 0)  
  43.         {  
  44.             strcpy(msg, "copy_data\n");  
  45.         }  
  46.         else  
  47.         {  
  48.             fgets(msg, MSG_SIZE, stdin);  
  49.         }  
  50.         //把互斥量mutex解锁,让其他的线程可以访问msg中的数据  
  51.         pthread_mutex_unlock(&mutex);  
  52.         sleep(1);//休眠1秒再继续循环,让其他线程有执行的机会  
  53.         pthread_mutex_lock(&mutex);  
  54.     }  
  55.     pthread_mutex_unlock(&mutex);  
  56.     printf("\nWaiting for thread finish...\n");  
  57.     //等待子线程结束  
  58.     res = pthread_join(thread, &thread_result);  
  59.     if(res != 0)  
  60.     {  
  61.         perror("pthread_join failed\n");  
  62.         exit(EXIT_FAILURE);  
  63.     }  
  64.     printf("Thread joined\n");  
  65.     //清理互斥量  
  66.     pthread_mutex_destroy(&mutex);  
  67.     exit(EXIT_SUCCESS);  
  68. }  
  69. void* thread_func(void *msg)  
  70. {  
  71.     int i = 0;  
  72.     char *ptr = msg;  
  73.     sleep(1);  
  74.     //把互斥量mutex加锁,以确保同一时间只有该线程可以访问msg中的数据  
  75.     pthread_mutex_lock(&mutex);  
  76.     while(strcmp("end\n", msg) != 0)  
  77.     {  
  78.         //把小写字母变成大写  
  79.         for(i = 0; ptr[i] != '\0'; ++i)  
  80.         {  
  81.             if(ptr[i] >= 'a' && ptr[i] <='z')  
  82.             {  
  83.                 ptr[i] -= 'a' - 'A';  
  84.             }  
  85.         }  
  86.         printf("You input %d characters\n", i-1);  
  87.         printf("To uppercase: %s\n", ptr);  
  88.         //把互斥量mutex解锁,让其他的线程可以访问msg中的数据  
  89.         pthread_mutex_unlock(&mutex);  
  90.         sleep(1);//休眠1秒再继续循环,让其他线程有执行的机会  
  91.         pthread_mutex_lock(&mutex);  
  92.     }  
  93.     pthread_mutex_unlock(&mutex);  
  94.     //退出线程  
  95.     pthread_exit(NULL);  
  96. }  

 

程序分析:

这个程序的工作流程已经说得非常清楚了,这里先来说说在main函数和线程函数thread_func中while循环中的sleep(1)语句的作用。可能很多人会认为这个sleep(1)是为了让子线程完成其处理和统计功能,所以要让主线程休眠1秒钟来等待子线程的处理统计工作的完成。的确在这里子线程进行的工作十分简单,1秒钟内的确可以处理统计完毕。但是这里的sleep(1)并不是为了实现这个功能,这两个循环中的sleep(1)是为了让其他的线程有机会被执行到,如果在一次的加锁和解锁之间没有这条语句的话,则当前的线程将会一直在循环中获得互斥量,因为其他的线程没有执行它的代码的时间,所以就要用这样的一条语句来给其他的线程一个运行的机会。如果子线程的执行时间超过1秒,这个程序还是会正常运行。

 

以这个例子来说,在主线程中,当输入数据完毕并对互斥量解锁之后,并不马上循环对其加锁,此时子线程就有了执行的机会,它会对互斥量进行加锁,同样地,当它处理统计完输入的数据后,它在进入下一次循环前,也休眠1秒,让主线程有机会再次运行。而主线程什么时候能够执行,取决于子线程何时对互斥量进行解锁。因为如果子线程拥有(锁住)互斥量,则主线程中函数pthread_mutex_lock就不会返回,使主线程处于阻塞状态。

 

换句话来说,就是只有子线程结束了对输入的处理和统计后,主线程才能继续执行,向msg中写入数据。看到这里,你应该知道之前在使用信号量时,我们多用一个信号量也是为了达到这个目的。所以当我们输入TEST时,程序有两个输入,但还是能正常运行,同样解决了之前使用一个信号量时所带来的问题。

 

信号量和互斥量的作用都是保护代码段的互斥设备,它们也非常相似。但在本例中,与使用信号量相比,实现同样的功能,如果使用信号量的话,则需要两个信号量,而使用互斥量的话,只需要一个。可以说在本例中,使用互斥量更简单。但是我觉得使用互斥量更容易犯错,我们可以看到在这个例子中,我们需要使用sleep语句来让其他线程获得执行的机会,但是在使用信号量的程序,它并不需要使用sleep,相对来说比较直观。我知道可能是我的实现方法不好,但是对于使用互斥量来说,我想了很久也想不到不使用sleep的方法。

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 40
码字总数 51358
作品 0
惠州
秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量

前面《秒杀多线程第四篇一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后...

晨曦之光
2012/05/21
83
0
秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量

前面《秒杀多线程第四篇一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后...

长平狐
2012/12/10
34
0
秒杀多线程第十五篇 关键段,事件,互斥量,信号量的“遗弃”问题

秒杀多线程第十五篇 关键段,事件,互斥量,信号量的“遗弃”问题 在《秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量》中对经典多线程同步互斥问题进行了回顾和总结,这篇文章对...

长平狐
2012/12/10
85
0
秒杀多线程第十一篇 读者写者问题

与上一篇《秒杀多线程第十篇 生产者消费者问题》的生产者消费者问题一样,读者写者也是一个非常著名的同步问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但...

长平狐
2012/12/10
139
0
秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: 《秒杀多线程第四篇一个经典的多线程同步问题》 《秒杀多线程第五篇经典线程同步关键段CS》 《秒杀多线程第六篇经典线程同步事件Event》 前面介绍了关键段...

长平狐
2012/12/10
65
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

[雪峰磁针石博客]软件测试专家工具包1web测试

web测试 本章主要涉及功能测试、自动化测试(参考: 软件自动化测试初学者忠告) 、接口测试(参考:10分钟学会API测试)、跨浏览器测试、可访问性测试和可用性测试的测试工具列表。 安全测试工具...

python测试开发人工智能安全
今天
2
0
JS:异步 - 面试惨案

为什么会写这篇文章,很明显不符合我的性格的东西,原因是前段时间参与了一个面试,对于很多程序员来说,面试时候多么的鸦雀无声,事后心里就有多么的千军万马。去掉最开始毕业干了一年的Jav...

xmqywx
今天
2
0
Win10 64位系统,PHP 扩展 curl插件

执行:1. 拷贝php安装目录下,libeay32.dll、ssleay32.dll 、 libssh2.dll 到 C:\windows\system32 目录。2. 拷贝php/ext目录下, php_curl.dll 到 C:\windows\system32 目录; 3. p...

放飞E梦想O
今天
0
0
谈谈神秘的ES6——(五)解构赋值【对象篇】

上一节课我们了解了有关数组的解构赋值相关内容,这节课,我们接着,来讲讲对象的解构赋值。 解构不仅可以用于数组,还可以用于对象。 let { foo, bar } = { foo: "aaa", bar: "bbb" };fo...

JandenMa
今天
1
0
OSChina 周一乱弹 —— 有人要给本汪介绍妹子啦

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享水木年华的单曲《中学时代》@小小编辑 手机党少年们想听歌,请使劲儿戳(这里) @须臾时光:夏天还在做最后的挣扎,但是晚上...

小小编辑
今天
48
8

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部