当前位置: 首页 > news >正文

代做淘宝客网站/深圳关键词推广整站优化

代做淘宝客网站,深圳关键词推广整站优化,传奇端游平台,织梦网站图标文章目录 线程安全问题的引入线程互斥互斥概念互斥锁互斥锁的计数器当中如何保证原子性互斥锁基础API初始化互斥锁变量函数动态初始化静态初始化 加锁函数阻塞加锁非阻塞加锁带有超时时间的加锁 解锁函数销毁互斥锁函数 线程同步线程同步的必要性条件变量条件变量的使用原理条件…

文章目录

  • 线程安全问题的引入
  • 线程互斥
    • 互斥概念
    • 互斥锁
    • 互斥锁的计数器当中如何保证原子性
    • 互斥锁基础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函数就没有被真正的执行完成,还处于函数内部抢锁的逻辑,然后一直来进行抢锁操作。

相关文章:

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

文章目录 线程安全问题的引入线程互斥互斥概念互斥锁互斥锁的计数器当中如何保证原子性互斥锁基础API初始化互斥锁变量函数动态初始化静态初始化 加锁函数阻塞加锁非阻塞加锁带有超时时间的加锁 解锁函数销毁互斥锁函数 线程同步线程同步的必要性条件变量条件变量的使用原理条件…...

1.初识爬虫

爬虫是批量模拟网络请求的程序&#xff0c;想百度谷歌这种搜索类网站本质上就是爬虫 使用爬虫的时候不应该对别人的网站有严重的影响&#xff0c;比如你爬的频率太高了&#xff0c;让人家的网站崩溃了。不应该爬取网页上显示不到的内容&#xff0c;比如有一个直播的网站&#…...

TLA+学习记录1——hello world

0x01 TLA是个好工具 编程人员一个好习惯是凡事都想偷懒&#xff0c;当然是指要科学地偷懒&#xff0c;而不是真的偷懒。一直想找到一种能检验写出的代码&#xff0c;做出的设计是否真的完全正确&#xff0c;而不是靠经验检视、代码Review、反复测试去检验。因为上述方法不管怎…...

基于QWebEngine实现无头浏览器

无头浏览器 无头浏览器&#xff08;Headless Browser&#xff09;是一种没有图形用户界面&#xff08;GUI&#xff09;的浏览器。它通过在内存中渲染页面&#xff0c;然后将结果发送回请求它的用户或程序来实现对网页的访问&#xff0c;而不会在屏幕上显示网页。这种方式使得无…...

编译Micropython固件For树莓派Raspberry Pi Pico

1. 前言 由于想把自己编写的py文件打包的固件中&#xff0c;所以记录下如何编译micropython固件和打包。 2. 编译 最简单的方式就是在你的树莓派上进行&#xff0c;我用的是RP Pi2 下载所需文件&#xff1a; $ cd ~/ $ mkdir pico $ cd pico $ git clone -b pico https://gi…...

基于googlenet网络的动物种类识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................. % 获取输入层的尺寸 Inp…...

如何用Jmeter编写脚本压测?

随着商业业务不断扩张&#xff0c;调用adsearch服务频率越来越高&#xff0c;所以这次想做个压测&#xff0c;了解目前多少并发量可以到达adsearch服务的界值。 这次选用的jmeter压测工具&#xff0c;压测思路如图&#xff1a; 一、日志入参 日志选取的adsearch 的 getads部分…...

SpingMVC之拦截器使用详解

拦截器概述 SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter&#xff0c;用于对处理器进行预处理和后处理。 过滤器和拦截器区别 过滤器&#xff1a;依赖于servlet容器。在实现上基于函数回调&#xff0c;可以对几乎所有请求进行过滤&#xff0c;但是缺点是一个过…...

motionface respeak新的aigc视频与音频对口型数字人

在当今的数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正在逐渐渗透到我们生活的方方面面。其中&#xff0c;AI技术在视频制作和处理领域的应用也日益广泛。本文将探讨如何利用AI技术实现视频中人脸与音频同步对口型的方法&#xff0c;旨在进一步丰富视频制作的效…...

【计算机网络】 静态库与动态库

文章目录 静态库实践使用方法总结 动态库实践使用方法总结 静态库与动态库的优缺点静态库优点缺点 动态库缺点优点 库有两种&#xff1a;静态库&#xff08;.a、.lib&#xff09;和动态库&#xff08;.so、.dll&#xff09;。所谓静态、动态是指链接。静态库是将整个库文件都拷…...

web端调用本地摄像头麦克风+WebRTC腾讯云,实现直播功能

目录 关于直播直播流程直播视频格式封装推流和拉流 获取摄像头和麦克风权限navigator.getUserMedia()MediaDevices.getUserMedia() WebRTC腾讯云快直播 关于直播 视频直播技术大全、直播架构、技术原理和实现思路方案整理 直播流程 视频采集端&#xff1a; 1、视频采集&#…...

React笔记(八)Redux

一、安装和配置 React 官方并没有提供对应的状态机插件&#xff0c;因此&#xff0c;我们需要下载第三方的状态机插件 —— Redux。 1、下载Redux 在终端中定位到项目根目录&#xff0c;然后执行以下命令下载 Redux npm i redux 2、创建配置文件 在 React 中&#xff0c;…...

数据库 | 数据库概述、关系型数据库、非关系型数据库

目录&#xff1a; 1.数据库&#xff1a;1.1 数据库的含义1.2 数据库的特点 2.数据表3.数据库管理系统4.数据库系统5.关系型数据库 和 非关系型数据库&#xff1a;5.1 关系型数据库5.2 关系型数据库“优势”5.3 非关系型数据库 6.关系型数据库 和 非关系型数据库 的“区别” 1.数…...

【备战csp-j】 csp常考题目详解(4)

四.数值转换与编码 1. 十进制数 11/128 可用二进制数码序列表示为( ) 。 A.1011/1000000 B.1011/100000000 C.0.001011 D.0.0001011 答案&#xff1a;D 解析&#xff1a;暂时未找到解决方法&#xff0c;以后会解决。 2. 算式(2047)10 &#xff0d; (3FF)16 &#xff0b; …...

linux中常见服务端安装

linux安装服务脚本 1、yum安装 # 通过apt安装yum apt install yum # yum安装软件 yum install pam-devel # yum 卸载 yum remove pam-devel2、rpm安装 # 安装 rpm -i example.rpm #安装 example.rpm 包&#xff1b; rpm -iv example.rpm #安装 example.rpm 包并在安装过程…...

L1-058 6翻了(Python实现) 测试点全过

前言&#xff1a; {\color{Blue}前言&#xff1a;} 前言&#xff1a; 本系列题使用的是&#xff0c;“PTA中的团体程序设计天梯赛——练习集”的题库&#xff0c;难度有L1、L2、L3三个等级&#xff0c;分别对应团体程序设计天梯赛的三个难度。更新取决于题目的难度&#xff0c;…...

初学Python记

Python这个编程语言的大名当然听说过了呀&#xff0c;这几年特别火&#xff0c;火的一塌涂地。大家可以回忆一下&#xff1a;朋友圈推荐的广告里经常可以看见python的网课广告。 本学期&#xff0c;学校开设了python课程&#xff0c;这几天学习了一下入了一下门&#xff0c;感…...

计算机竞赛 基于深度学习的目标检测算法

文章目录 1 简介2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 1 简介 &#x1f5…...

sentinel-core

引入依赖<dependencies><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId></dependency><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-anno…...

【美团3.18校招真题1】

大厂笔试真题网址&#xff1a;https://codefun2000.com/ 塔子哥刷题网站博客&#xff1a;https://blog.codefun2000.com/ 小美剪彩带 提交网址&#xff1a;https://codefun2000.com/p/P1088 题意&#xff1a;找出区间内不超过k种数字子数组的最大长度 使用双指针的方式&…...

Springboot 实践(14)spring config 配置与运用--手动刷新

前文讲解Spring Cloud zuul 实现了SpringbootAction-One和SpringbootAction-two两个项目的路由切换&#xff0c;正确访问到项目中的资源。这两个项目各自拥有一份application.yml项目配置文件&#xff0c;配置文件中有一部分相同的配置参数&#xff0c;如果涉及到修改&#xf…...

MyBatisPlus枚举类最佳实践(非常典型和高效的枚举类写法)

目录 1、MyBatisPlus枚举类最佳实践 2、枚举类的作用及问题 3、MyBatisPlus注解实现枚举最佳实践 4、简单来说 5、下面我们看一个使用上述注解的完整枚举类示例: &#xff08;1&#xff09;枚举类&#xff1a; &#xff08;2&#xff09;DTO类&#xff1a; 6、根据上面…...

uniapp分包 解决分多个包的问题

1. 分包可以分很多个, 但是在"optimization": { "subPackages": true } 里面只能写一个, 2. 想分多个包 , 在 pages.json 里面 的 subPackages 里面继续加 第三个 第四个即可 3. 保存之后 创建页面就可以看见多个包了...

美国封锁激励中国制造业数字化转型的崛起 | 百能云芯

上海在近日公布了第二批工赋链主培育企业名单&#xff0c;共有15家企业入选。这些被称为“链主”的企业在上海制造业数字化转型的过程中扮演着关键角色&#xff0c;类似于领头大雁&#xff0c;它们是上海制造业的数字化网络中的关键节点。 中新社的报道指出&#xff0c;“数字技…...

鼠标键盘自动化工具pyautogui

安装 pip install pyautogui pip install keyboard获取鼠标实时位置 import pyautogui pyautogui.displayMousePosition()样例代码 # https://pyautogui.readthedocs.org/ # https://github.com/asweigart/pyautogui# 紧急停止&#xff0c;手动将鼠标移动到屏幕的4个角落imp…...

0基础学习VR全景平台篇 第96篇:VR电子楼书

大家好&#xff0c;欢迎观看蛙色VR官方系列课程——VR电子楼书&#xff01; 作为2021年底全新上线的行业解决方案&#xff0c;是专门针对地产、园区数字化营销的一站式VR解决方案&#xff0c;为行业潜在客户提供优质的7x24小时线上看房体验。 本期教程将通过功能介绍后台操作&…...

【MySQL】数据库的约束

MySQL 数据库的约束 文章目录 MySQL 数据库的约束01 数据库的约束1.1 约束类型1.1.1 NOT NULL1.1.2 UNIQUE1.1.3 DEFAULT1.1.4 PRIMARY KEY1.1.5 FOREIGN KEY1.1.6 CHECK 继上文 MySQL基础&#xff08;一&#xff09;&#xff0c; MySQL基础&#xff08;二&#xff09;&#…...

改变金融贷款市场营销方式 ---- 运营商大数据精准获客

与传统的企业网络营销相比&#xff0c;最常见的是网络推广和硬广告推广。一些企业无法找到可靠准确的数据来源&#xff0c;也无法找到一些未知的总数据。这些数据大多存在持续时间长、准确性差的缺点&#xff0c;企业在将这些数据信息应用于商品在线营销时往往会遇到不足。 在…...

SpringBoot实现分页的四种方式

一 自己封装Page对象实现 博客链接 二 使用sql实现分页 2.1 场景分析 前段传递给给后台什么参数? 当前页码currentPage每页显示条数pageSize 后台给前端返回什么数据? 当前页数据List总记录数totalCount 2.2 前段代码 <template><el-paginationsize-change&q…...

远程工作面试:特殊情况下的面试技巧

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...