【Linux】线程安全-互斥同步

news2024/11/15 15:52:15

文章目录

  • 线程安全问题的引入
  • 线程互斥
    • 互斥概念
    • 互斥锁
    • 互斥锁的计数器当中如何保证原子性
    • 互斥锁基础API
      • 初始化互斥锁变量函数
        • 动态初始化
        • 静态初始化
      • 加锁函数
        • 阻塞加锁
        • 非阻塞加锁
        • 带有超时时间的加锁
      • 解锁函数
      • 销毁互斥锁函数
  • 线程同步
  • 线程同步的必要性
  • 条件变量
    • 条件变量的使用原理
    • 条件变量的原理
    • 条件变量基础API
      • 初始化条件变量函数
        • 动态初始化
        • 静态初始化
      • 销毁条件变量函数
      • 等待条件变量函数
      • 唤醒条件变量函数
        • 单个唤醒
        • 广播唤醒
    • 条件变量常见问题

线程安全问题的引入

使用一个 抢票程序 演示线程安全的概念及重要性:

代码如下:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<pthread.h>
    4 int g_tickets = 100;
W>  5 void* mythread_start(void* arg){
    6   while(1){
    7     if(g_tickets <= 0)
    8       break;
W>  9     printf("I am %p, I have ticket %d\n", pthread_self(),g_tickets--);
   10     //usleep(100);
   11     //--g_tickets;
   12   }
W> 13 }                                                                   
   14 int main(){     
W> 15   int i = 0;
   16   pthread_t tid[2];
   17   for(int i=0; i<2; ++i){
   18     int ret = pthread_create(&tid[i], NULL, mythread_start, NULL);                               
   19     if(ret < 0){   
   20       perror("pthread_create");
   21       return 0;                                                   
   22     }                                                             
   23     for(int i=0; i<2; ++i){    
   24       pthread_join(tid[i],NULL);//设置为阻塞
   25     }
   26   }
   27   return 0;
   28 }

执行结果:

在这里插入图片描述

结果分析:

我们代码的预期目标是创造两个黄牛(线程),让这两个线程去抢票,我们观察执行可以不难看出,抢票活动只有一个黄牛在参与,两外一个线程并没有参与,这是怎么回事呢?这是正常现象吗?

首先这是正常的现象和结果:我们称两个线程分贝为A和B,A首先被创建出来,A被创建出来后是不会等B创建出来再一起去抢票的,我们之前强调过,线程是被操作系统独立调度的,所以,先被创建出来的A线程就先执行了抢票程序,拿到了全部的票。

那什么样的结果算是有问题的呢?A和B抢到了同一张票,或者抢到了不合法的票(负数)

为了让两个黄牛都能抢到票,我们每次抢票结束后都让黄牛休息一下

代码如下:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<pthread.h>
    4 int g_tickets = 100;
W>  5 void* mythread_start(void* arg){
    6   while(1){
    7     if(g_tickets <= 0)
    8       break;
W>  9     printf("I am %p, I have ticket %d\n", pthread_self(),g_tickets);
   10     usleep(100);
   11     --g_tickets;
   12   }
W> 13 }                                                                   
   14 int main(){     
W> 15   int i = 0;
   16   pthread_t tid[2];
   17   for(int i=0; i<2; ++i){
   18     int ret = pthread_create(&tid[i], NULL, mythread_start, NULL);                               
   19     if(ret < 0){   
   20       perror("pthread_create");
   21       return 0;                                                   
   22     }                                                             
   23     for(int i=0; i<2; ++i){    
   24       pthread_join(tid[i],NULL);//设置为阻塞
   25     }
   26   }
   27   return 0;
   28 }

执行结果:

在这里插入图片描述

出现了段错误,这是什么原因呢?因为我们出现了二义性,在同一时刻两个线程对同一个数据进行修改。

错误分析:

1、假设同一个程序中有两个线程:A和B,A和B同时对一个int类型的全局变量n=10在其各自的线程入口函数中对这样一个全局变量进行加加操作
2、当A拥有CPU之后,对n进行++操作是非原子性的操作,也就是说,这个操作随时可能会被打断,假设A被调度,刚把全局变量的数值10读取到CPU的寄存器中时就被切换出去了(程序计数器中保存着线程A下一步要执行的指令,上下文信息中保存寄存器的值,这两个东西是为了当线程A再次拥有CPU使用权的时候,还原当时线程A切换出去的线程现场使用的)
3、当线程A被切换出去后,这会儿,可能线程B获取了CPU时间片,被调度,B对全局变量进行了++操作,此时全局变量从10变成了11,并且回写到内存中去
4、当线程A再次拥有CPU时间片之后,恢复当时切换出去的现场,继续往下执行,由于上下文信息中保存的全局变量仍然是10,执行完++操作变成11,然后再回写到内存中去,全局变量的值仍任是11
5、虽然两个线程都对这个全局变量进行了++操作,但是从值上面来看,全局变量仅仅进行了一次++操作,这就是线程的不安全

线程互斥

互斥概念

互斥要做的事情:控制线程的访问时序,保证各个线程对共享资源的独占式访问。当多个线程能够同时访问到临界资源的时候,有可能会导致线程执行的结果产生二义性。而互斥就是要保证多个线程在访问同一个临界资源,执行临界区代码的时候 (非原子性性操作(线程可以被打断) ),控制访问时序。让一个线程独占临界资源执行完,再让另外一个独占执行。

临界资源:能被多个线程同时访问到的资源
临界区代码:访问临界资源的代码

互斥锁

互斥锁的本质就是0/1计数器,计数器的取值只能为0/1

  • 0:表示当前线程不可以获取到互斥锁,也就不能访问临界区资源
  • 1:表示当前线程可以获取到互斥锁,可以访问临界资源

需要注意的是:并不是说线程不获取互斥锁就不能访问临界资源,而是程序员需要在代码中用同一个互斥锁去约束多个线程,意思就是说,在加锁时,必须加的是同一把锁,当线程A拿到这把锁将这把锁的信号量改成0其他线程就无法访问了,但是当线程A被切换出去之后,其他线程加锁加的也是这把被A置为0的锁,只有当A访问结束将这把锁置为1其他线程才可以访问临界区资源,否则A加锁访问,B访问临界资源之前不加锁,这样也不能约束B,这就叫防君子不防小人,也就是说在每一个线程代码中要访问一个临界区资源之前要先获取锁,也就是加锁。

为了避免出现两个线程可能同时加锁成功,我们需要互斥锁本身就是原子性的

互斥锁的计数器当中如何保证原子性

为什么计数器(锁)当中的值从0变成1,或者从1变成0是原子性的呢?

为了保证互斥锁操作,大多数体系结构都提供了swap和exchange指令,该指令作用是把寄存器和内存单元的数据相交换,由于只有一条指令,只会出现执行成功和未执行两种情况,所以是原子性的

加锁的时候(将寄存器当中的值设置为0)

  • 第一种情况:计数器的值为1,说明锁空闲,没有被线程加锁
  • 交换情况:加锁成功
  • 第二种情况:计数器的值为0,说明锁忙碌,被其他线程加锁拿走(此时当前线程进入等待状态:等待其他线程访问完毕将锁打开)
  • 交换情况:加锁失败

解锁的时候(将寄存器当中的值设置为1)

  • 计数器的值为0,需要解锁, 进行一步交换
  • 交换成功:解锁成功
  • 交换失败:解锁失败

互斥锁基础API

互斥锁的相关函数主要有以下5个:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 这些函数的第一个参数mutex都是一个pthread_mutex_t结构体指针变量,指向要操作的目标互斥锁结构体
  • 参数attr是一个pthread_mutexattr_t结构指针变量,指向互斥锁属性结构体,如果填入NULL,表示使用默认属性

初始化互斥锁变量函数

动态初始化

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

功能:初始化一个互斥锁结构体的属性

参数:

  • pthread_mutex_t* : 一个指向pthread_mutex_t结构体的指针变量
  • pthread_mutexattr_t* : 一个指向pthread_mutexattr_t结构的指针变量

返回值:成功返回0,失败返回errno

静态初始化

使用宏PTHREAD_MUTEX_INITIALIZER来初始化一个互斥锁结构体,实际上该宏是将互斥锁的各个字段初始化为0

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//初始化一个mutex锁

宏定义源码:

#define PTHREAD_MUTEX_INITIALIZER \
{ { 0, 0, 0, 0, 0, __PTHREAD_SPINS, { 0, 0 } } }

加锁函数

阻塞加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:给临界区代码阻塞等待加锁

参数:pthread_mutex_t* : 一个指向pthread_mutex_t结构体的指针变量

返回值:返回0表示成功;加锁失败返回errno

阻塞加锁解释:

  • 如果mutex中互斥量的值为1,则pthread_mutex_lock函数就返回0表示加锁成功
  • 如果mutex中互斥量的值为0,则线程阻塞在pthread_mutex_lock函数中,直到加锁成功

非阻塞加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

功能:给临界区代码非阻塞加锁

参数:pthread_mutex_t* : 一个指向pthread_mutex_t结构体的指针变量

返回值:返回0表示成功;加锁失败返回errno

非阻塞加锁解释:

  • 如果mutex中互斥量的值为1,则pthread_mutex_trylock函数就返回0表示加锁成功
  • 如果mutex中互斥量的值为0,则pthread_mutex_trylock函数就返回错误码EBUSY

非阻塞加锁,拿锁的时候如果拿不到锁就直接返回了,也不等待,需要搭配循环使用,一直调用,当其返回值为zero时循环结束,否则使用这个接口的后果就和我们上面说的,当一个进程对一块被占用的资源访问时,如果不拿到这个锁,也是可以访问的,这样就达不到互斥访问的目的了

带有超时时间的加锁

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

功能:如果不能立即获得目标互斥锁,则等待abs_timeout时间,如果在等待时间内加锁成功则直接返回,如果超过等待时间则直接返回表示加锁失败

解锁函数

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:将互斥锁中的互斥量从0变成1,表示其他线程可以获取该互斥锁了。以原子操作的方式给一个互斥锁解锁,如果此时有其他线程正在等待这个互斥锁,则其他线程会获得该互斥锁

参数:pthread_mutex_t* : 一个指向pthread_mutex_t结构体的指针变量

返回值:返回0表示解锁成功;解锁失败返回errno

销毁互斥锁函数

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:将目标互斥锁销毁

参数:pthread_mutex_t* : 一个指向pthread_mutex_t结构体的指针变量

返回值:销毁成功返回0;销毁失败返回errno

如果是动态初始化互斥锁,就需要销毁,如果是静态初始化就不用销毁。

在所有线程可能退出的地方进行解锁,防止产生死锁

代码演示:

我们对上面的黄牛抢票的程序进行修改,代码如下:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<pthread.h>
    4 int g_tickets = 100;
    5 pthread_mutex_t lock;
W>  6 void* mythread_start(void* arg){
    7   pthread_mutex_lock(&lock);//加锁
    8   while(g_tickets--){
W>  9     printf("I am %p, I have ticket %d\n", pthread_self(),g_tickets);
   10   }
W> 11 }
   12 int main(){
   13   pthread_mutex_init(&lock, NULL);//动态初始化
   14   pthread_t tid[2];
   15   for(int i=0; i<2; ++i){                                                                   
   16     int ret = pthread_create(&tid[i], NULL, mythread_start, NULL);
   17     if(ret < 0){
   18       perror("pthread_create");
   19       return 0;
   20     }
   21   }
   22   while(1){
   23     sleep(1);
   24   }
   25   pthread_mutex_destroy(&lock);//退出时销毁互斥锁
   26   return 0;
   27 }

执行结果:

在这里插入图片描述

结果分析:

在这里插入图片描述

我们查看进程的调用栈,发现除了主线程外,只有一个线程,这个工作线程一直在等待锁,但是加锁的线程已经将锁锁上,而且执行完代码后退出了,现在这把锁就被永远的锁上,这种情况叫死锁
上述导致死锁的原因是线程退出时没有进行解锁,那防止死锁的产生也就需要在线程所有可能退出的地方进行解锁。

我们对代码进行改进,前五十张票让第一个线程拿,也就是全局变量小于50时,第一个线程进行解锁,让另一个线程可以接手,修改代码如下:

在这里插入图片描述

执行结果:

在这里插入图片描述

这下子并没有再出现死锁的情况了

结论:在线程所有有可能退出的地方都进行解锁,防止产生死锁,不要让线程退出的时候把锁带走

线程同步

线程同步的必要性

多个线程保证了互斥, 也就是保证了线程能够合理的访问临界资源了。但并不是说, 各个线程在访问临界资源的时候都是合理的。同步是为了保证多个线程对临界资源的访问的合理性这个合理性建立在多个线程保证互斥的情况下。就比如说一个吃面的场景,当一个只有一个碗,吃面和做面的人都可以对这个碗进行访问操作,而对这个碗同时只能有一个人访问,不可以同时做面的人在往碗里做面吃面的人也在碗中吃面,要防止这样的情况发生就要采用互斥的原理,但是当有了互斥之后保证线程可以独自访问资源了,就如吃面的人可以独自吃面了而不会有做面的人来干扰,而做面的人也可以独自做面了,也不会有吃面的人同时和它抢生意,但是还有一个问题,就是你吃面的人不可以在碗里没有面的情况下去吃面,甚至把碗吃掉,你做面的人不可以在碗里有面的情况下,再去做面,那碗里都盛不下面了,所以此时要有同步的概念来保证访问临界资源的合理性。

同步:保证各个线程对于共享资源的访问具有合理性

模拟一下上面的场景,代码如下:

    1 #include<stdio.h>
    2 #include<pthread.h>
    3 #include<unistd.h>
    4 pthread_mutex_t g_bowl;//互斥锁,碗
    5 int bowl = 1;
W>  6 void* eat_thread_start(void* arg){
    7   while(1){
    8     pthread_mutex_lock(&g_bowl);
    9     printf("I am eatman eat%d\n",bowl--);
   10     pthread_mutex_unlock(&g_bowl);
   11   }
   12 }
W> 13 void* make_thread_start(void* arg){
   14   while(1){
   15     pthread_mutex_lock(&g_bowl);
   16     printf("I am makeman make%d\n",++bowl);
   17     pthread_mutex_unlock(&g_bowl);
   18   }
   19 }
   20 int main(){                                                                                 
   21   pthread_mutex_init(&g_bowl, NULL);
   22   pthread_t eat_tid;
   23   pthread_t make_tid;
   24   int ret = pthread_create(&eat_tid, NULL, eat_thread_start, NULL);
   25   if(ret < 0){
   26     perror("pthread_create");
   27   }
   28   ret = pthread_create(&make_tid, NULL, make_thread_start, NULL);
   29   if(ret < 0){
   30     perror("pthread_create");
   31   }
   32   pthread_join(eat_tid, NULL);
   33   pthread_join(make_tid, NULL);
   34   pthread_mutex_destroy(&g_bowl);
   35   return 0;
   36 }

执行结果:

在这里插入图片描述

做面做出了负数,出现了不合理的访问。

为了控制线程对共享资源的独占式访问,并且访问次序具有合理性,有以下方式实现线程同步

  • POSIX信号量
  • 条件变量

什么是对共享资源的独占式访问呢?

同学们在上厕所的时候,坑位就是一个共享资源,同学们在上厕所时,将厕所门关上,就是对共享资源的独占式访问,在你上厕所的时候,别人进不来(无法访问),引申到代码中,就是相当:将对于一个变量的操作变成原子性操作,要么操作成功,要么没操作,不存在操作一半被打断的情况

条件变量

条件变量的使用原理

  • 线程在加锁后,判断下临界资源是否可用
  • 如果可用,则直接访问临界资源
  • 如果不可用,则调用等待接口,让该线程进行等待

改进代码:

在这里插入图片描述

执行结果:可以看到这里就正常了。做面人不会因为碗里有面再做面,吃面人不会因为碗里没有面而把碗吃掉

在这里插入图片描述

但是上述代码有很严重的效率缺陷,十分耗费CPU资源,还不拿CPU资源干正事儿,假设此时一个线程拿到的时间片是200ms,该线程在该时间片内判断,吃面人要吃面但是如果碗里没面他就continue退出这一次循环,但是由于时间片没有结束,就会再次进行循环,加锁判断是否有面,但是还是没有面,就再次退出循环,如此反复,假设一次他加锁后,还没进行解锁,时间片就用完了,下一个线程要执行,要拿到互斥锁,但是此时互斥锁没有被释放,这个线程又要在所有的时间片内重复判断,但是这些举动是毫无意义的,程序效率就很低下

条件变量的原理

条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程
条件变量本质是一个PCB等待队列(存放在等待的线程的PCB)

条件变量基础API

条件变量的相关函数主要以下几个:

#include <pthread.h>
int pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t * attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, struct timespec * abstime);
int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_t是条件变量结构体
  • pthread_condattr_t是条件变量属性结构体
  • PTHREAD_COND_INITIALIZER是一个宏,用来初始化条件变量结构体的,本质是将条件变量各个字段设置为0

初始化条件变量函数

动态初始化

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

功能:初始化条件变量结构体

参数:

  • pthread_cond_t : 条件变量结构体指针
  • pthread_condattr_t : 条件变量属性结构体指针,常传入NULL,使用默认的属性
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);

静态初始化

使用宏PTHREAD_COND_INITIALIZER初始化条件变量

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量函数

int pthread_cond_destroy(pthread_cond_t *cond);

功能:销毁一个条件变量

参数:pthread_cond_t : 条件变量结构体指针

pthread_cond_destroy(&cond)

等待条件变量函数

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

功能:将调用该函数的线程放入PCB等待队列中

参数:

  • pthread_cond_t : 条件变量结构体指针

  • mutex : 该线程等待的互斥锁

唤醒条件变量函数

单个唤醒

int pthread_cond_signal(pthread_cond_t *cond);

功能:唤醒一个等待目标条件变量的线程,至于唤醒的是PCB等待队列当中的哪个线程,取决于线程的优先级和调度策略,(有可能唤醒两个或者三个或者全部都唤醒)

参数:pthread_cond_t : 条件变量结构体指针

广播唤醒

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:以广播的方式唤醒所有等待目标条件变量的线程

参数:pthread_cond_t : 条件变量结构体指针

代码演示:

在这里插入图片描述

执行结果:吃面人吃面,做面人做面,很和谐

在这里插入图片描述

将上述程序的进程数量增加,比如说有两个做面人和两个吃面人,此时又会出现新的问题:当wait结束后,执行的是printf,也就是吃面/做面,再之后,我们的if判断就不起作用了,就会出现疯狂的做面和吃面,所以我们在wait结束后,printf之前,也就是吃面/做面前,再次判断是否能吃面/做面

代码如下:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#define countman 2
pthread_mutex_t g_bowl;//互斥锁,碗
pthread_cond_t g_cond;//条件变量
int bowl = 1;
void* eat_thread_start(void* arg){
  while(1){
    pthread_mutex_lock(&g_bowl);
    if(bowl==0){
      //如果碗里没有面了,就不能吃面了
      pthread_mutex_unlock(&g_bowl);
      pthread_cond_wait(&g_cond, &g_bowl);//没面了等做面人做面
    }
    printf("I am eatman eat%d\n",bowl--);
    pthread_mutex_unlock(&g_bowl);
    pthread_cond_signal(&g_cond);//面吃完了通知做面人来做面
  }
}
void* make_thread_start(void* arg){
  while(1){
    pthread_mutex_lock(&g_bowl);
    if(bowl==1){
      //如果碗里有面就不再做面了
      pthread_mutex_unlock(&g_bowl);
      pthread_cond_wait(&g_cond, &g_bowl);//等待吃面人吃面,没碗装面了
    }
    printf("I am makeman make%d\n",++bowl);
    pthread_mutex_unlock(&g_bowl);
    pthread_cond_signal(&g_cond);//面做好了通知吃面人吃面
  }
}
int main(){
  pthread_mutex_init(&g_bowl, NULL);
  pthread_t eat_tid[countman];
  pthread_t make_tid[countman];
  for(int i=0; i<countman; ++i){
    int ret = pthread_create(&eat_tid[i], NULL, eat_thread_start, NULL);
    if(ret < 0){
      perror("pthread_create");
     }
    ret = pthread_create(&make_tid[i], NULL, make_thread_start, NULL);
    if(ret < 0){
      perror("pthread_create");
    }
  }
  for(int i=0; i<countman; ++i){
    pthread_join(eat_tid[i], NULL);
    pthread_join(make_tid[i], NULL);
  }
  pthread_mutex_destroy(&g_bowl);
  pthread_cond_destroy(&g_cond);
  return 0;
}

执行结果:

在这里插入图片描述

改进如下:

在这里插入图片描述

执行结果:我们虽然解决了刚刚多个吃面线程乱吃的问题,但是这里发现运行到最后程序不跑了

在这里插入图片描述

查看一下调用栈:

在这里插入图片描述

这样的一个场景就是所有线程都进入了等待队列中,但是没有人通知。说到底产生这样的问题是因为所有做面线程已经进入等待队列当中去了,但是此时吃面进程欲要通知等待队列中的做面线程但是它有可能将吃面线程通知出来,导致所有线程进入等待状态。

解决程序卡死方式:我们想要解决这个问题就要让做面线程每次唤醒的都是吃面线程,而吃面线程每次唤醒的都是做面线程。(当然也可以用int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒PCB等待队列中所有的线程,但是种不推荐使用,因为太浪费CPU资源)

改进如下:

在这里插入图片描述

执行结果:

在这里插入图片描述

发现不会再卡死或者疯狂吃面或者疯狂做面的情况,针对不同的资源,可以设置不同的条件变量

条件变量常见问题

条件变量的等待接口第二个参数为什么会有互斥锁?

当线程被放入等待队列后,就不会再执行后续代码了,也就不会再进行解锁了,锁将永远被锁上,其他线程就不会获得这把锁来进行操作,所以我们要将互斥锁传入条件变量等待函数,在该函数内部进行解锁操作,让其他线程可以拿到锁

pthread_cond_wait的内部是针对互斥锁做了上什么操作?先释放互斥锁还是先将线程放入到PCB等待队列?

pthread_cond_wait在调用的时候先要将线程放入等待队列当中。然后再释放互斥锁。

我们假设是先解锁然后再让线程入等待队列,这样的话,有可能这个线程刚解锁,就有其他线程拿到锁,并且修改了临界区资源,而此时的临界区资源被修改后恰好满足条件,所以这个线程就唤醒等待队列当中等待资源的线程,但是现在等待队列中还没有线程,它唤醒之后也进入了等待队列进行等待,此时当此线程进入等待队列当中时,之前的线程也刚刚到等待队列,那么此时两个线程就同时进到等待队列中了,谁也出不去

线程被唤醒之后会执行什么代码,为什么需要获取互斥锁?

pthread_cond_wait在返回之前一定会在其内部进行加锁操作,就是当一个线程在调用pthread_cond_wait函数进入等待队列中后,然后被唤醒时一定会在pthread_cond_wait函数中执行加锁操作。
而在加锁操作时:

  • 抢到了:pthread_cond_wait函数就真正的执行完毕,函数返回。
  • 没抢到:pthread_cond_wait函数就没有被真正的执行完成,还处于函数内部抢锁的逻辑,然后一直来进行抢锁操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/958659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

冠达管理:龙头股票是什么意思?

龙头股票是指在某个职业或板块中市值最大、盈余才能最强、发展前景最好的上市公司。可以说&#xff0c;龙头股票是该职业或板块的代表。 那么&#xff0c;为什么龙头股票具有如此重要的地位&#xff1f;与其他股票比较&#xff0c;有哪些优势和下风&#xff1f;这篇文章将从多…

Stable Diffusion 从入门到企业级应用010

一、前言 本文是《Stable Diffusion 从入门到企业级应用实战》系列的第四部分能力进阶篇《Stable Diffusion ControlNet v1.1 图像精准控制》的第010篇 利用Stable Diffusion ControlNet 法线贴图模型精准控制图像生成。本部分内容&#xff0c;位于整个Stable Diffusion生态体…

节能减排数远程控制二次开发网关BL304

钡铼技术嵌入式ARM控制器BL304在各个领域的应用越来越广泛。从物联网、工业物联网&#xff0c;到数字化工厂、工业自动化&#xff0c;再到智慧医疗、智慧电力、智慧安防&#xff0c;以及车载、轨道交通、通讯、充电桩、智能家居、人机交互等&#xff0c;BL304都发挥着举足轻重的…

哈弗枭龙MAX将在9月上旬推出首次OTA升级,保不住电?不存在的

9月1日消息&#xff0c;哈弗品牌执行副总经理乔心昱昨晚通过个人微博向网友们回应了关于哈弗枭龙MAX电池保持的问题。乔心昱表示&#xff0c;在与紧急技术团队确认后&#xff0c;他首先澄清了一个误会&#xff1a;大家可以放心使用&#xff0c;正常情况下没有电池问题。 此外&a…

2023开学啦《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

2023开学啦《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

【UE 材质】常用向量运算节点——点积、叉积、归一化

目录 一、点积 二、叉积 三、归一化 一、点积 点积&#xff0c;也称为内积或数量积&#xff0c;是一种用于计算两个向量之间关系的操作。对于两个三维向量 A&#xff08;a1,a2,a3&#xff09;和 B(b1,b2,b3)&#xff0c;它们的点积可以用以下公式表示&#xff1a; ABa1​⋅…

【业务功能篇93】微服务-springcloud-多线程-异步处理-异步编排-CompletableFutrue-实战运用

异步处理编排 我们可以在商品详细信息查询的位置实现CompletableFuture的异步编排处理。 根据业务分析&#xff1a;3.4.5数据接口的入参信息需要来源于1数据接口的返回信息&#xff0c;也就是skuid 所以可以设计 1 3 4 5 串行线程 &#xff0c;而 3 4 5依赖1 &#xff0c;需要等…

2023下半年深圳软考信息系统项目管理师认证开班中,快来报名

信息系统项目管理师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资…

运营超5000万公里,再签700辆订单,嬴彻卡车NOA引领商用车自动驾驶商业化

从主动安全到智能驾驶&#xff0c;商用车自动驾驶商业化进程已经明显提速。 8月29日&#xff0c;嬴彻科技举办以“奔跑吧 卡车NOA”为主题的第二届嬴彻科技日&#xff0c;宣布嬴彻卡车NOA&#xff08;导航辅助驾驶&#xff09;已经突破5000万公里&#xff0c;并实现安全运营零…

骨传导耳机有副作用吗?骨感耳机有什么弊端?

骨传导耳机是通过振动骨骼传达声音信号到内耳而非通过耳道传输的&#xff0c;因此相较于传统耳机&#xff0c;其对耳道和鼓膜的刺激较小&#xff0c;可以说骨传导耳机在使用中是没有副作用的。 不过&#xff0c;任何产品都不是十全十美的&#xff0c;那么骨传导耳机有什么弊端…

python 笔记(3)——request

目录 1、使用requests发送http请求 1-1&#xff09;发送get请求 1-2&#xff09;发送 post 请求 1-3&#xff09;发送 get 请求下载网络图片 1-4&#xff09;使用 post 上传文件 1-5&#xff09;自动维护 session 的方式 2、使用 os.popen 执行cmd命令 3、基于 beautif…

Git学习——细节补充

Git学习——细节补充 1. git diff2. git log3. git reset4. git reflog5. 提交撤销5.1 当你改乱了工作区某个文件的内容&#xff0c;想直接丢弃工作区的修改时5.2 当提交到了stage区后&#xff0c;想要退回 6. git remote7. git pull origin master --no-rebase8. 分支管理9. g…

【100天精通python】Day46:python网络编程_网络编程基础与入门

目录 专栏导读 1 网络编程的基础 2. 基本概念和协议 2.1 计算机网络基础 2.2 网络协议、IP地址、端口号 2.3 常见网络协议 3. 套接字编程 3.1 套接字的基本概念 3.2 套接字的基本操作 3.3 套接字通信模型和方法&#xff1a;send、recv 3.3.1 TCP通信模型 3.3.2 U…

linux-samba-window登不上

登不上查了很久发现是防火墙导致的 sudo firewall-cmd --list-all //查看所有的防火墙信息sudo firewall-cmd --permanent --zonepublic --add-servicesamba //service里添加sambafirewall-cmd --reload //重启 便可以登录了,小问题

VirtualBox7+Ubuntu22集群规划

1. 目的: 新入手了一台小主机&#xff08;8核 / Intel(R) Xeon(R) W-10885M CPU 2.40GHz 2.40 GHz, 16vCpu / 64G RAM / 系统类型 64 位操作系统, 基于 x64 的处理器&#xff09;&#xff0c;原装了一套Win11专业版&#xff0c;打算用VirtualBox 虚拟一个集群。 2. …

电脑可以上网,微信都可以用,但浏览器打不开网页

可以试试设置DNS&#xff08;其他windows版本步骤&#xff09;&#xff1a; 1.打开控制面板 2.网络和Internet 3.查看网络计算机和设备 4.按照下图步骤&#xff1a; 5.按下图进行

深度学习论文分享(九)Unifying Motion Deblurring and Frame Interpolation with Events

深度学习论文分享&#xff08;九&#xff09;Unifying Motion Deblurring and Frame Interpolation with Events 前言Abstract1. Introduction2. Related Work2.1. Frame Interpolation2.2. Motion Deblurring2.3. Joint Deblurring and Interpolation 3. Problem Statement4. …

BL122:钡铼技术驱动,实现无缝Modbus到Profinet转换

钡铼技术BL122协议转换网关是一款专业的设备&#xff0c;用于将Modbus协议转换为Profinet协议。BL122采用了先进的嵌入式ARM MCU和基于Linux OS系统的开发&#xff0c;具备出色的性能和高度的稳定性。 BL122支持多种下行采集方式&#xff0c;包括Modbus RTU、Modbus TCP和Modbu…

Playwright for Python:安装及初步使用

文章目录 一、Playwright介绍1.1 简单介绍1.2 支持的平台1.3 支持语言1.4 官方文档&#xff08;python&#xff09; 二、开始2.1 安装要求2.2 安装2.3 脚本录制2.4 代码示例 一、Playwright介绍 1.1 简单介绍 Playwright是微软推出来的一款自动化测试工具&#xff0c;是专门为…

机器视觉工程师,没有错误

记住&#xff0c;在生活中&#xff0c;自己要会享受。在工作&#xff0c;做到一丝不苟。在技术上&#xff0c;追求精益。 人生不是那么一帆风顺&#xff0c;更不是坎坎坷坷&#xff0c;希望你们是曲折上升的&#xff0c;虽不能大富大贵&#xff0c;只要自己努力完全可以小康。…