|
|
51CTO旗下网站
|
|
移动端

Linux互斥锁之线程互斥锁

在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

作者:土豆居士来源:一口Linux|2020-08-26 08:59

 

在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

Linux实现的互斥锁机制包括POSIX互斥锁和内核互斥锁,本文主要讲POSIX互斥锁,即线程间互斥锁。

“ 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在 那里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”

也就是说,信号量不一定是锁定某一个资源,而是 流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算 或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

两者之间的区别:

作用域

信号量 : 进程间或线程间(linux仅线程间)

互斥锁 : 线程间

上锁时

信号量 : 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0 。

互斥锁 : 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数 。

多线程

线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。与多进程相比,多进程具有多进程不具备的一些优点,其最重要的是:对于多线程来说,其能够比多进程更加节省资源。

线程创建

在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。

在Linux中,通过函数pthread_create()函数实现线程的创建:

pthread_create()

  1. int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*st 

其中:

  • thread表示的是一个pthread_t类型的指针;
  • attr用于指定线程的一些属性;
  • start_routine表示的是一个函数指针,该函数是线程调用函数;
  • arg表示的是传递给线程调用函数的参数。

当线程创建成功时,函数pthread_create()返回0,若返回值不为0则表示创建线程失败。对于线程的属性,则在结构体pthread_attr_t中定义。

线程创建的过程如下所示:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <unistd.h> 
  4. #include <malloc.h> 
  5.  
  6. void* thread(void *id){ 
  7.    pthread_t newthid; 
  8.  
  9.    newthid = pthread_self(); 
  10.    printf("this is a new thread, thread ID is %u\n", newthid); 
  11.    return NULL
  12.  
  13. int main(){ 
  14.  int num_thread = 5; 
  15.  pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  16.  
  17.  printf("main thread, ID is %u\n", pthread_self()); 
  18.  for (int i = 0; i < num_thread; i++){ 
  19.        if (pthread_create(&pt[i], NULL, thread, NULL) != 0){ 
  20.           printf("thread create failed!\n"); 
  21.           return 1; 
  22.        } 
  23.  } 
  24.  sleep(2); 
  25.  free(pt); 
  26.  return 0; 

在上述代码中,使用到了pthread_self()函数,该函数的作用是获取本线程的线程ID。在主函数中的sleep()用于将主进程处于等待状态,以让线程执行完成。最终的执行效果如下所示:

那么,如何利用arg向子线程传递参数呢?其具体的实现如下所示:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <unistd.h> 
  4. #include <malloc.h> 
  5.  
  6. void* thread(void *id){ 
  7.   pthread_t newthid; 
  8.  
  9.   newthid = pthread_self(); 
  10.   int num = *(int *)id; 
  11.   printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); 
  12.   return NULL
  13.  
  14. int main(){ 
  15.   //pthread_t thid; 
  16.   int num_thread = 5; 
  17.   pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  18.   int * id = (int *)malloc(sizeof(int) * num_thread); 
  19.  
  20.   printf("main thread, ID is %u\n", pthread_self()); 
  21.   for (int i = 0; i < num_thread; i++){ 
  22.      id[i] = i; 
  23.      if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
  24.         printf("thread create failed!\n"); 
  25.         return 1; 
  26.      } 
  27.   } 
  28.   sleep(2); 
  29.   free(pt); 
  30.   free(id); 
  31.   return 0; 

其最终的执行效果如下图所示:

如果在主进程提前结束,会出现什么情况呢?如下述的代码:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <unistd.h> 
  4. #include <malloc.h> 
  5.  
  6. void* thread(void *id){ 
  7.   pthread_t newthid; 
  8.  
  9.   newthid = pthread_self(); 
  10.   int num = *(int *)id; 
  11.   printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); 
  12.   sleep(2); 
  13.   printf("thread %u is done!\n", newthid); 
  14.   return NULL
  15.  
  16. int main(){ 
  17.   //pthread_t thid; 
  18.   int num_thread = 5; 
  19.   pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  20.   int * id = (int *)malloc(sizeof(int) * num_thread); 
  21.  
  22.   printf("main thread, ID is %u\n", pthread_self()); 
  23.   for (int i = 0; i < num_thread; i++){ 
  24.      id[i] = i; 
  25.      if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
  26.         printf("thread create failed!\n"); 
  27.         return 1; 
  28.      } 
  29.    } 
  30.    //sleep(2); 
  31.    free(pt); 
  32.    free(id); 
  33.    return 0; 

此时,主进程提前结束,进程会将资源回收,此时,线程都将退出执行,运行结果如下所示:

线程挂起

在上述的实现过程中,为了使得主线程能够等待每一个子线程执行完成后再退出,使用了free()函数,在Linux的多线程中,也可以使用pthread_join()函数用于等待其他线程,函数的具体形式为:

  1. int pthread_join(pthread_t thread, void **retval); 

函数pthread_join()用来等待一个线程的结束,其调用这将被挂起。

一个线程仅允许一个线程使用pthread_join()等待它的终止。

如需要在主线程中等待每一个子线程的结束,如下述代码所示:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <unistd.h> 
  4. #include <malloc.h> 
  5.  
  6. void* thread(void *id){ 
  7.   pthread_t newthid; 
  8.  
  9.   newthid = pthread_self(); 
  10.   int num = *(int *)id; 
  11.   printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); 
  12.  
  13.   printf("thread %u is done\n", newthid); 
  14.   return NULL
  15.  
  16. int main(){ 
  17.    int num_thread = 5; 
  18.    pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  19.    int * id = (int *)malloc(sizeof(int) * num_thread); 
  20.  
  21.    printf("main thread, ID is %u\n", pthread_self()); 
  22.    for (int i = 0; i < num_thread; i++){ 
  23.       id[i] = i; 
  24.       if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
  25.          printf("thread create failed!\n"); 
  26.          return 1; 
  27.        } 
  28.    } 
  29.    for (int i = 0; i < num_thread; i++){ 
  30.       pthread_join(pt[i], NULL); 
  31.    } 
  32.    free(pt); 
  33.    free(id); 
  34.    return 0; 

最终的执行效果如下所示:

注:在编译的时候需要链接libpthread.a:

g++ xx.c -lpthread -o xx

互斥锁mutex

多线程的问题引入

多线程的最大的特点是资源的共享,但是,当多个线程同时去操作(同时去改变)一个临界资源时,会破坏临界资源。如利用多线程同时写一个文件:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <malloc.h> 
  4.  
  5. const char filename[] = "hello"
  6.  
  7. void* thread(void *id){ 
  8.   int num = *(int *)id; 
  9.  
  10.   // 写文件的操作 
  11.   FILE *fp = fopen(filename, "a+"); 
  12.   int start = *((int *)id); 
  13.   int end = start + 1; 
  14.   setbuf(fp, NULL);// 设置缓冲区的大小 
  15.   fprintf(stdout, "%d\n", start); 
  16.   for (int i = (start * 10); i < (end * 10); i ++){ 
  17.       fprintf(fp, "%d\t", i); 
  18.   } 
  19.   fprintf(fp, "\n"); 
  20.   fclose(fp); 
  21.   return NULL
  22.  
  23. int main(){ 
  24.    int num_thread = 5; 
  25.    pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  26.    int * id = (int *)malloc(sizeof(int) * num_thread); 
  27.  
  28.    for (int i = 0; i < num_thread; i++){ 
  29.       id[i] = i; 
  30.       if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
  31.          printf("thread create failed!\n"); 
  32.          return 1; 
  33.          } 
  34.    } 
  35.    for (int i = 0; i < num_thread; i++){ 
  36.       pthread_join(pt[i], NULL); 
  37.    } 
  38.    // 释放资源 
  39.    free(pt); 
  40.    free(id); 
  41.    return 0; 

执行以上的代码,我们会发现,得到的结果是混乱的,出现上述的最主要的原因是,我们在编写多线程代码的过程中,每一个线程都尝试去写同一个文件,这样便出现了上述的问题,这便是共享资源的同步问题,在Linux编程中,线程同步的处理方法包括:信号量,互斥锁和条件变量。

互斥锁

互斥锁是通过锁的机制来实现线程间的同步问题。互斥锁的基本流程为:

  • 初始化一个互斥锁:pthread_mutex_init()函数
  • 加锁:pthread_mutex_lock()函数或者pthread_mutex_trylock()函数
  • 对共享资源的操作
  • 解锁:pthread_mutex_unlock()函数
  • 注销互斥锁:pthread_mutex_destory()函数

其中,在加锁过程中,pthread_mutex_lock()函数和pthread_mutex_trylock()函数的过程略有不同:

  • 当使用pthread_mutex_lock()函数进行加锁时,若此时已经被锁,则尝试加锁的线程会被阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock()函数有返回值时,说明加锁成功;
  • 而使用pthread_mutex_trylock()函数进行加锁时,若此时已经被锁,则会返回EBUSY的错误码。

同时,解锁的过程中,也需要满足两个条件:

  • 解锁前,互斥锁必须处于锁定状态;
  • 必须由加锁的线程进行解锁。

当互斥锁使用完成后,必须进行清除。

有了以上的准备,我们重新实现上述的多线程写操作,其实现代码如下所示:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <malloc.h> 
  4.  
  5. pthread_mutex_t mutex; 
  6.  
  7. const char filename[] = "hello"
  8.  
  9. void* thread(void *id){ 
  10.  
  11.    int num = *(int *)id; 
  12.    // 加锁 
  13.  
  14.    if (pthread_mutex_lock(&mutex) != 0){ 
  15.      fprintf(stdout, "lock error!\n"); 
  16.    } 
  17.    // 写文件的操作 
  18.    FILE *fp = fopen(filename, "a+"); 
  19.    int start = *((int *)id); 
  20.    int end = start + 1; 
  21.    setbuf(fp, NULL);// 设置缓冲区的大小 
  22.    fprintf(stdout, "%d\n", start); 
  23.    for (int i = (start * 10); i < (end * 10); i ++){ 
  24.       fprintf(fp, "%d\t", i); 
  25.    } 
  26.    fprintf(fp, "\n"); 
  27.    fclose(fp); 
  28.  
  29.    // 解锁 
  30.    pthread_mutex_unlock(&mutex); 
  31.    return NULL
  32.  
  33. int main(){ 
  34.    int num_thread = 5; 
  35.    pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); 
  36.    int * id = (int *)malloc(sizeof(int) * num_thread); 
  37.  
  38.    // 初始化互斥锁 
  39.    if (pthread_mutex_init(&mutex, NULL) != 0){ 
  40.      // 互斥锁初始化失败 
  41.      free(pt); 
  42.      free(id); 
  43.      return 1; 
  44.    } 
  45.    for (int i = 0; i < num_thread; i++){ 
  46.       id[i] = i; 
  47.       if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ 
  48.          printf("thread create failed!\n"); 
  49.          return 1; 
  50.       } 
  51.    } 
  52.    for (int i = 0; i < num_thread; i++){ 
  53.       pthread_join(pt[i], NULL); 
  54.    } 
  55.    pthread_mutex_destroy(&mutex); 
  56.    // 释放资源 
  57.    free(pt); 
  58.    free(id); 
  59.    return 0; 

最终的结果为:

参考文章:

http://www.broadview.com.cn/article/297

https://www.cnblogs.com/jingzhishen/p/3807455.html

本文转载自微信公众号「一口Linux」,可以通过以下二维码关注。转载本文请联系一口Linux公众号。

【编辑推荐】

  1. 合并和排序 Linux 上的文件
  2. Linux终端里的记录器
  3. 如何在Linux上使用xargs命令
  4. 合并和排序Linux上的文件
  5. Linux端口转发的几种常用方法
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

136人订阅学习

搭建数据中心实验Lab

搭建数据中心实验Lab

实验平台Datacenter
共5章 | ITGO(老曾)

93人订阅学习

大数据安全运维实战

大数据安全运维实战

CDH+Ambari
共20章 | 大数据陈浩

91人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微