前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux线程编程同步之互斥锁和条件变量

Linux线程编程同步之互斥锁和条件变量

作者头像
用户6280468
发布2022-03-21 08:47:17
1.6K0
发布2022-03-21 08:47:17
举报
文章被收录于专栏:txp玩Linux

今天是最后一篇关于Linux线程编程的文章分享,在这里我们先掌握基础的概念及其应用,后面在慢慢去深入学习。最近看到一句说的非常在理:理论’是你知道是这样,但它却不好用。‘实践’是它很好用,但你不知道是为什么。我想大多数学习者,和我一样,在学习的过程中,都会或多或少的有这种情况,不过自己坚信,你把基础打好(同时学的过程中,不要好高骛远,三心二意的,把自己先暂时用到的东西学明白,再去学其他东西,不要当前的,没学会,又跑去学其他的,而且又学不会,这样浪费时间和精力;这个这里基础打好,举个例子,你的c语言功底要打好,对指针的使用非常熟悉,甚至一些高级用法就是要平时慢慢积累和总结,以及内存原理要知道为什么是这样等方面),后面实战的话,就好多了,至少不会说我这个东西不会那个东西又不会,这样会让自己很痛苦当初为啥没学好基础,现在实战中漏洞百出。好了,废话不多说了,开始下面的主题分享:

一、互斥锁

1、什么是互斥锁?

这里的话,我举一个日常生活的例子来引入这个概念(不是很好,不要见外,主要是为了好理解,哈哈。)。当你去厕所上厕所的时候,你的先打开厕所的门来,不然进不去;进去之后,你又得把门关上,当你上厕所上到一半的时候,突然外面有人也要来上厕所了,但是你把门给锁住了,所以他就知道里面有人在上厕所,这个人就只能在外面等你上完厕所出来,他才能进去上厕所。

我们要讲的互斥锁和上面举得不是很好的例子,不过道理是一样的:当多线程中的一个线程正在访问一个共享变量时,它会先上锁(也就是说上锁之后,其他线程不能对这个共享变量操作了,其他线程处于等待状态),然后对这个共享变量操作使用完之后,它才会把这个锁给打开,接着给其他线程来使用这个共享变量,其它线程在操作这个共享变量的时候,也是按照这个规律来操作的,这样的话,就能实现多线程的同步了(这里的同步,是多线程对共享的变量达到相同的操作)。

2、互斥锁操作函数介绍

——注意在使用man手册查看这些互斥锁函数的时候,你会发现找不到,这里你得先安装它的包,使用这个命令来安装:sudo apt-get install manpages-posix-dev,如下图所示

a、互斥锁初始化函数:

代码语言:javascript
复制
 #include <pthread.h>

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

   pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

参数说明:

上面的第一个参数就是我们要操作的共享变量,第二个参数一般默认使用NULL。

b、解锁和上锁函数:

代码语言:javascript
复制
#include <pthread.h>

   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_trylock(pthread_mutex_t *mutex);
   int pthread_mutex_unlock(pthread_mutex_t *mutex);

DESCRIPTION
   The mutex object referenced by mutex shall be locked by a call to pthread_mutex_lock() that returns zero or [EOWNERDEAD].  If  the  mutex  is  already  locked  by  another
   thread,  the  calling  thread shall block until the mutex becomes available. This operation shall return with the mutex object referenced by mutex in the locked state with
   the calling thread as its owner. If a thread attempts to relock a mutex that it has already locked, pthread_mutex_lock() shall behave as described in the Relock column  of
   the  following  table.  If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, pthread_mutex_unlock() shall behave as described in the
   Unlock When Not Owner column of the following table.

函数返回值:

代码语言:javascript
复制
RETURN VALUE
   If successful, the pthread_mutex_lock(), pthread_mutex_trylock(), and pthread_mutex_unlock() functions shall return zero; otherwise, an error number shall be  returned  to
   indicate the error.

ERRORS
   The pthread_mutex_lock() and pthread_mutex_trylock() functions shall fail if:

   EAGAIN The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded.

   EINVAL The  mutex was created with the protocol attribute having the value PTHREAD_PRIO_PROTECT and the calling thread's priority is higher than the mutex's current prior‐
          ity ceiling.

   ENOTRECOVERABLE
          The state protected by the mutex is not recoverable.

   EOWNERDEAD
          The mutex is a robust mutex and the process containing the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired  by  the
          calling thread and it is up to the new owner to make the state consistent.

   The pthread_mutex_lock() function shall fail if:

   EDEADLK
          The mutex type is PTHREAD_MUTEX_ERRORCHECK and the current thread already owns the mutex.

   The pthread_mutex_trylock() function shall fail if:

   EBUSY  The mutex could not be acquired because it was already locked.

   The pthread_mutex_unlock() function shall fail if:

   EPERM  The mutex type is PTHREAD_MUTEX_ERRORCHECK or PTHREAD_MUTEX_RECURSIVE, or the mutex is a robust mutex, and the current thread does not own the mutex.

   The pthread_mutex_lock() and pthread_mutex_trylock() functions may fail if:

   EOWNERDEAD
          The mutex is a robust mutex and the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired by the calling thread and it is
          up to the new owner to make the state consistent.

   The pthread_mutex_lock() function may fail if:
   EOWNERDEAD
          The mutex is a robust mutex and the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired by the calling thread and it is
          up to the new owner to make the state consistent.

   The pthread_mutex_lock() function may fail if:

   EDEADLK
          A deadlock condition was detected.

   These functions shall not return an error code of [EINTR].

   The following sections are informative.

参数说明:

这里面的参数和上面使用一样。不过要注意函数pthread_mutex_trylock会尝试对互斥量加锁,如果该互斥量已经被锁住,函数调用失败,返回EBUSY,否则加锁成功返回0,线程不会被阻塞;而函数pthread_mutex_lock返回成功时,不会这样,会用阻塞。

c、锁资源释放函数:

代码语言:javascript
复制
#include <pthread.h>

   int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

这里参数的话,和上面使用一样,但是要注意这个函数的使用,它是可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。

3、代码实战演示:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

char buf[200] = {0};
pthread_mutex_t mutex;
unsigned int flag = 0;

// 子线程程序,作用是统计buf中的字符个数并打印
 void *func(void *arg)
 {
// 子线程首先应该有个循环
// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
// 长度,然后打印;完成后再次被阻塞

//while (strncmp(buf, "end", 3) != 0)
    sleep(1);
   while (flag == 0)
   {    
       pthread_mutex_lock(&mutex);
       printf("本次输入了%d个字符\n", strlen(buf));
       memset(buf, 0, sizeof(buf));
       pthread_mutex_unlock(&mutex);
       sleep(1);
     }


     pthread_exit(NULL);
}


int main(void)
{
      int ret = -1;
      pthread_t th = -1;

      pthread_mutex_init(&mutex, NULL);

      ret = pthread_create(&th, NULL, func, NULL);
      if (ret != 0)
      {
            printf("pthread_create error.\n");
            exit(-1);
      }

      printf("输入一个字符串,以回车结束\n");
      while (1)
      {
            pthread_mutex_lock(&mutex);
            scanf("%s", buf);
            pthread_mutex_unlock(&mutex);
    // 去比较用户输入的是不是end,如果是则退出,如果不是则继续        
            if (!strncmp(buf, "end", 3))
           {
           printf("程序结束\n");
           flag = 1;

           //exit(0);
           break;
           }
           sleep(1);
    // 主线程在收到用户收入的字符串,并且确认不是end后
    // 就去发信号激活子线程来计数。
    // 子线程被阻塞,主线程可以激活,这就是线程的同步问题。
    // 信号量就可以用来实现这个线程同步
       }


// 回收子线程
       printf("等待回收子线程\n");
       ret = pthread_join(th, NULL);
       if (ret != 0)
       {
            printf("pthread_join error.\n");
            exit(-1);
       }
       printf("子线程回收成功\n");

       pthread_mutex_destroy(&mutex);

       return 0;
}

演示结果:

代码语言:javascript
复制
 root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
 输入一个字符串,以回车结束
 jkdjk
 本次输入了5个字符
 jkdjkdj
 本次输入了7个字符
 end
 程序结束
 等待回收子线程
 本次输入了3个字符
 子线程回收成功

说明:

上面的演示是使用了上一篇的代码演示,上一篇文章里面我们使用了信号量来实现多线程同步操作,这里是使用互斥锁来实现多线程。

二、条件变量

1、什么是条件变量:

我们还是以日常生活的中例子来引出概念来:在我们去医院看病的时候,你的先挂号才能看病,而且人非常多,一个医生根本忙不过来(大量任务需要处理),所以每个看病的人挂号时间非常短暂的就能解决掉(每个任务的处理时间很短),所以我们一般去医院挂号,会有许多窗口的,但是还是有时候要排队的,然后类似下面这种场景,大家肯定都遇到过的:

  • 每个挂号窗口不会去主动争抢客户(加锁任务队列),而只依次处理在自己窗口下排队的客户(每个工作线程有自己的任务队列)
  • 来挂号的病人会自己选择队伍最短的窗口去排队,或者有时候大堂保安会负责指挥大家到哪里排队(任务分发线程把任务直接分配到某个工作线程的任务队列)。
  • 在排队人数比较多的时候,医院会开放更多的挂号窗口(启动更多工作线程),因为在不同窗口下的队列之间没有任何竞争关系,新增的挂号窗口能够缓解挂号人多的压力。

通过上面的例子,我们可以看出,条件变量与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止(比如挂号的人都弄完了,它就会自动停止挂号)。但是通常条件变量和互斥锁同时使用(如上面的例子,各个窗口挂号互不干扰)。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。总之条件变量要和互斥锁一起来用使用。

2、条件变量函数介绍:

a、初始化函数

代码语言:javascript
复制
#include <pthread.h>

   int pthread_cond_destroy(pthread_cond_t *cond);

   int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

   pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

说明:

函数pthread_cond_init里面的第一个参数就是我们要使用的那个条件变量来控制线程间的同步,第一个参数一般默为NULL;函数pthread_cond_destroy里面的参数也是一个条件变量。

原注释:

代码语言:javascript
复制
The pthread_cond_destroy() function shall destroy the given condition variable specified by cond; the object becomes, in effect, uninitialized. An implementation may cause
   pthread_cond_destroy()  to set the object referenced by cond to an invalid value. A destroyed condition variable object can be reinitialized using pthread_cond_init(); the
   results of otherwise referencing the object after it has been destroyed are undefined.

   It shall be safe to destroy an initialized condition variable upon which no threads are currently blocked. Attempting to destroy a  condition  variable  upon  which  other
   threads are currently blocked results in undefined behavior.
代码语言:javascript
复制
   The  pthread_cond_init()  function  shall  initialize the condition variable referenced by cond with attributes referenced by attr.  If attr is NULL, the default condition
   variable attributes shall be used; the effect is the same as passing the address of a default condition variable attributes object.  Upon  successful  initialization,  the
   state of the condition variable shall become initialized.

   Only  cond  itself  may  be  used  for  performing  synchronization.  The  result of referring to copies of cond in calls to pthread_cond_wait(), pthread_cond_timedwait(),
   pthread_cond_signal(), pthread_cond_broadcast(), and pthread_cond_destroy() is undefined.

b、发送信号函数:

代码语言:javascript
复制
#include <pthread.h>

   int pthread_cond_broadcast(pthread_cond_t *cond);
   int pthread_cond_signal(pthread_cond_t *cond);

描述:

代码语言:javascript
复制
 DESCRIPTION
   These functions shall unblock threads blocked on a condition variable.

   The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.

   The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond  (if  any  threads  are  blocked  on
   cond).

   If more than one thread is blocked on a condition variable, the scheduling policy shall determine the order in which threads are unblocked. When each thread unblocked as a
   result of a pthread_cond_broadcast() or pthread_cond_signal() returns from its call to pthread_cond_wait() or pthread_cond_timedwait(), the thread shall own the mutex with
   which  it  called  pthread_cond_wait()  or  pthread_cond_timedwait().   The thread(s) that are unblocked shall contend for the mutex according to the scheduling policy (if
   applicable), and as if each had called pthread_mutex_lock().

   The pthread_cond_broadcast() or pthread_cond_signal()  functions  may  be  called  by  a  thread  whether  or  not  it  currently  owns  the  mutex  that  threads  calling
   pthread_cond_wait()  or  pthread_cond_timedwait()  have associated with the condition variable during their waits; however, if predictable scheduling behavior is required,
   then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().

   The pthread_cond_broadcast() and pthread_cond_signal() functions shall have no effect if there are no threads currently blocked on cond.

   The behavior is undefined if the value specified by the cond argument to pthread_cond_broadcast() or pthread_cond_signal() does not refer to an initialized condition vari‐
   able.

返回值:

代码语言:javascript
复制
  RETURN VALUE
      If successful, the pthread_cond_broadcast() and pthread_cond_signal() functions shall return zero; otherwise, an error number shall be returned to indicate the error.

ERRORS
   These functions shall not return an error code of 
[EINTR].

   The following sections are informative.

说明:

上面的pthread_cond_signal 函数和 pthread_cond_broadcast函数 的区别在于:

  • pthread_cond_signal 只保证唤醒至少一个被阻塞的线程。
  • pthread_cond_broadcast 会唤醒所有阻塞在条件变量 cond上的线程。

c、等待函数:

代码语言:javascript
复制
   #include <pthread.h>


   int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

说明:

第一个参数是条件变量,第二个参数就是我们的互斥锁的共享变量了。

3、代码示例演示:

代码语言:javascript
复制
   #include <stdio.h>
   #include <string.h>
   #include <stdlib.h>
   #include <pthread.h>

   char buf[200] = {0};
   pthread_mutex_t mutex;
   pthread_cond_t cond;
   unsigned int flag = 0;


  // 子线程程序,作用是统计buf中的字符个数并打印
   void *func(void *arg)
   {
// 子线程首先应该有个循环
// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
// 长度,然后打印;完成后再次被阻塞

//while (strncmp(buf, "end", 3) != 0)
//sleep(1);
       while (flag == 0)
      { 
        pthread_mutex_lock(&mutex);
       pthread_cond_wait(&cond, &mutex);
       printf("本次输入了%d个字符\n", strlen(buf));
       memset(buf, 0, sizeof(buf));
       pthread_mutex_unlock(&mutex);
    //sleep(1);
       }


      pthread_exit(NULL);
  }


 int main(void)
 {
     int ret = -1;
    pthread_t th = -1;


    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    ret = pthread_create(&th, NULL, func, NULL);
       if (ret != 0)
       {
        printf("pthread_create error.\n");
        exit(-1);
       }

    printf("输入一个字符串,以回车结束\n");
    while (1)
    {
        //pthread_mutex_lock(&mutex);
        scanf("%s", buf);
        pthread_cond_signal(&cond);
        //pthread_mutex_unlock(&mutex);
        // 去比较用户输入的是不是end,如果是则退出,如果不是则继续        
     if (!strncmp(buf, "end", 3))
    {
        printf("程序结束\n");
        flag = 1;

        //exit(0);
        break;
    }
          //sleep(1);
          // 主线程在收到用户收入的字符串,并且确认不是end后
          // 就去发信号激活子线程来计数。
          // 子线程被阻塞,主线程可以激活,这就是线程的同步问题。
         // 信号量就可以用来实现这个线程同步
    }
      // 回收子线程
       printf("等待回收子线程\n");
       ret = pthread_join(th, NULL);
       if (ret != 0)
      {
            printf("pthread_join error.\n");
          exit(-1);
      }
     printf("子线程回收成功\n");

      pthread_mutex_destroy(&mutex);
      pthread_cond_destroy(&cond);

      return 0;
 }

演示结果:

代码语言:javascript
复制
 root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
 输入一个字符串,以回车结束
 djdkj
 本次输入了5个字符
 djkdjaskldjfaks
 本次输入了15个字符
 jdkajdkljasdf
 本次输入了13个字符
 edn
 本次输入了3个字符
 end
程序结束
等待回收子线程
本次输入了3个字符
子线程回收成功

三、总结:

以上就是Linux系统编程专题的全部了,当然只是介绍了一些基础入门的东西,不过你的掌握入门,才会有知识能力去看懂更深一点的东西,后期也会分享高级应用的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档