Linux编程基础 6.2:线程同步

2 线程同步

  • 线程同步中的“同步”与生活中大家认知的“同步”略有不同,“同”不指同时,其主旨在于协同步调,按预定的先后次序执行线程;
  • 之所以需要实现线程同步,是因为若不对线程的执行次序加以控制,可能会出现数据混乱。

出现与事件有关的错误的原因有三个:

  • (1)资源共享;
  • (2)调度随机;
  • (3)线程间缺乏必要的同步机制。

Linux系统实现线程同步的方式常用的有三种:

  • 互斥锁;
  • 条件变量;
  • 信号量。

2.1 互斥锁

使用互斥锁的实现线程同步时主要操作分为四步:
①初始化互斥锁:pthread_mutex_init
②加锁:pthread_mutex_lock
③解锁:pthread_mutex_unlock
④销毁锁:pthread_mutxe_destroy

2.1.1 初始化互斥锁

#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

功能:初始化互斥锁。

参数说明

  • mutex:一个传入传出参数:
    – ①pthread_mutext_t的本质是结构体,为简化理解,读者可将其视为整型;
    – ②变量mutex只有两种取值:0和1,加锁:mutex-1;解锁:mutex+1;
    – ③参数mutex之前的restrict是一个关键字,该关键字用于限制指针,功能是告诉编译器,所有修改该指针指向内容的操作,只能通过本指针完成。
  • attr:一个传入传出参数,代表互斥量的属性,通常传NULL,表示使用默认属性。

返回值说明

  • 成功:返回0;
  • 不成功:返回errno,errno的常见取值为EAGAIN和EDEADLK,其中EAGAIN表示超出互斥锁递归锁定的最大次数,因此无法获取该互斥锁;EDEADLK表示当前线程已有互斥锁,二次加锁失败。

2.1.2 加锁

#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:锁定指定互斥量。

参数说明

  • mutex:待锁定的互斥量。

返回值说明

  • 成功:返回0;
  • 不成功:返回errno。
#include <pthread.h>int pthread_mutex_trylock(pthread_mutex_t *mutex);

功能:尝试加锁,若锁正在被使用,不阻塞等待,而是直接返回并返回错误号。

参数说明

  • mutex:待锁定的互斥量。

返回值说明

  • 成功:返回0;
  • 不成功:返回errno,其中常见的errno有两个,分别为EBUSY和EAGAIN,它们代表的含义如下:
    – EBUSY:参数mutex指向的互斥锁已锁定;
    – EAGAIN:超过互斥锁递归锁定的最大次数。

2.1.3 解锁

#include <pthread.h>int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:为指定互斥量解锁。

参数说明

  • mutex:待解锁的互斥量。

返回值说明

  • 成功:返回0;
  • 不成功:返回errno。

2.1.4 销毁锁

#include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:为指定互斥量销毁。

参数说明

  • mutex:待销毁的互斥量。

返回值说明

  • 成功:返回0;
  • 不成功:返回errno。

【案例 1】在原线程和新线程中分别进行打印操作,使原线程分别打印“HELLO”、“ WORLD”,新线程分别打印“hello”、“world”。

//未添加mutex
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *paraArg) {srand(time(NULL));while (1) {printf("hello ");//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误sleep(rand() % 3);printf("world\n");sleep(rand() % 3);}//of whilereturn NULL;
}//of tfn
int main(void) {pthread_t tempTid;srand(time(NULL));pthread_create(&tempTid, NULL, tfn, NULL);while (1) {printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);}//of whilepthread_join(tempTid, NULL);return 0;
}//of main

未添加互斥量,会导致打印乱序。

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t globMutux;						//定义互斥锁
void err_thread(int paraRet, char *paraStr) {if (paraRet != 0) {fprintf(stderr, "%s:%s\n", paraStr, strerror(paraRet));pthread_exit(NULL);}//of if
}//of err_thread
void *tfn(void *paraArg) {srand(time(NULL));while (1) {pthread_mutex_lock(&globMutux);     		//加锁:m--printf("hello ");//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误sleep(rand() % 3);printf("world\n");pthread_mutex_unlock(&globMutux);   		//解锁:m++sleep(rand() % 3);}//of whilereturn NULL;
}//of tfn
int main(void) {pthread_t tempTid;srand(time(NULL));int tempFlag = 5;pthread_mutex_init(&globMutux, NULL);        	//初始化mutex:m=1int tempRet = pthread_create(&tempTid, NULL, tfn, NULL);err_thread(tempRet, "pthread_create error");while (tempFlag--) {pthread_mutex_lock(&globMutux);     		//加锁:m--printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");pthread_mutex_unlock(&globMutux);     	//解锁:m--sleep(rand() % 3);}//of whilepthread_cancel(tempTid);pthread_join(tempTid, NULL);pthread_mutex_destroy(&globMutux);return 0;
}//of main

2.2 条件变量

使用条件变量控制线程同步时,线程访问共享资源的前提,是程序中设置的条件变量得到满足。条件变量不会对共享资源加锁,但也会使线程阻塞,若线程不满足条件变量规定的条件,就会进入阻塞状态直到条件满足。
条件变量的使用分为以下四个步骤:

  • (1)初始化条件变量:pthread_cond_init();
  • (2)等待条件变量满足:pthread_cond_wait();
  • (3)唤醒阻塞线程:pthread_cond_signal()、pthread_cond_broadcast();
  • (4)释放条件变量:pthread_cond_destroy()。

2.2.1 初始化条件变量

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

功能:初始化条件变量。

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型;
  • attr:代表条件变量的属性:
    – NULL:表示使用默认属性初始化条件变量;
    – PTHREAD_PROCESS_PRIVATE:表示当前进程中的线程共用此条件变量;
    – PTHREAD_PROCESS_SHARED:表示多个进程间的线程共用条件变量。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1并设置errno。

2.2.2 等待条件变量满足

#include <pthread.h>int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

功能:使线程进入阻塞状态,等待一个条件变量后继续执行。pthread_cond_wait类似于互斥锁的pthread_mutex_lock函数,但其功能更为丰富,它的工作机制如下:

  • (1)阻塞等待条件变量cond满足;
  • (2)解除已绑定的互斥锁(类似于pthread_mutex_unlock);
  • (3)当线程被唤醒,pthread_cond_wait函数返回,pthread_cond_wait函数同时会解除线程阻塞,并使线程重新申请绑定互斥锁。
    在这里插入图片描述
    条件变量控制流程示意图

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型;
  • mutex:代表与当前线程绑定的互斥锁。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1并设置errno。
#include <pthread.h>int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
struct timespec {time_t tv_sec;		//秒long tv_nsec;		//纳秒
};

功能:使线程阻塞等待条件变量,不同的是,该函数可以指定线程的阻塞时长,若等待超时,该函数便会返回。

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型;
  • mutex:代表与当前线程绑定的互斥锁;
  • abstime:等待时长。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1并设置errno。

2.2.3 唤醒条件变量

#include <pthread.h>int pthread_cond_signal(pthread_cond_t *cond);

功能:在条件变量满足之后,以信号的形式唤醒阻塞在该条件变量的一个线程。处于阻塞状态中的线程的唤醒顺序由调度策略决定。

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1并设置errno。
#include <pthread.h>int pthread_cond_broadcast(pthread_cond_t *cond);

功能:唤醒阻塞在指定条件变量的线程,不同的是,该函数会以广播的形式,唤醒阻塞在该条件变量上的所有线程。

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1并设置errno。

2.2.4 销毁条件变量

#include <pthread.h>int pthread_cond_destory(pthread_cond_t *cond);

功能:当没有线程在等待参数cond指定的条件变量时,才可以销毁条件变量。

参数说明

  • cond:代表条件变量,一个指向pthread_cond_t的结构体指针,pthread_cond_t是Linux系统定义的条件变量类型。

返回值说明

  • 成功:返回0;
  • 不成功:返回EBUSY。

【案例2】生产者-消费者模型是线程同步中的一个经典案例。假设有两个线程,这两个线程同时操作一个共享资源(一般称为汇聚),其中一个模拟生产者行为,生产共享资源,当容器存满时,生产者无法向其中放入产品;另一个线程模拟消费者行为,消费共享资源,当产品数量为0时,消费者无法获取产品,应阻塞等待。显然,为防止数据混乱,每次只能由生产者、消费者中的一个,操作共享资源。本案例要求使用程序实现简单的生产者-消费者模型(可假设容器无限大)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
struct msg {struct msg *next;int num;
};
struct msg *globHead;
pthread_cond_t globHasProduct = PTHREAD_COND_INITIALIZER;	//初始化条件变量
pthread_mutex_t globLock = PTHREAD_MUTEX_INITIALIZER;		//初始化互斥锁
//消费者
void *consumer(void *paraP) {struct msg *tempMsgP;for (;;) {pthread_mutex_lock(&globLock);						//加锁//若头结点为空,表明产品数量为0,消费者无法消费产品while (globHead == NULL) {pthread_cond_wait(&globHasProduct, &globLock);		//阻塞等待并解锁}//of whiletempMsgP = globHead;globHead = tempMsgP->next;    					//模拟消费一个产品pthread_mutex_unlock(&globLock);printf("-Consume ---%d\n", tempMsgP->num);free(tempMsgP);sleep(rand() % 5);}//of for
}//of consumer
//生产者
void *producer(void *paraP) {struct msg *tempMsgP;while (1) {tempMsgP = malloc(sizeof(struct msg));tempMsgP->num = rand() % 1000 + 1;        	//模拟生产一个产品printf("-Produce ---%d\n", tempMsgP->num);pthread_mutex_lock(&globLock);				//加锁tempMsgP->next = globHead;					//插入结点(添加产品)globHead = tempMsgP;pthread_mutex_unlock(&globLock);				//解锁pthread_cond_signal(&globHasProduct);  		//唤醒等待在该条件变量上的一个线程sleep(rand() % 5);}//of while
}//of producer
int main(int argc, char *argv[]) {pthread_t tempPid, tempCid;srand(time(NULL));//创建生产者、消费者线程pthread_create(&tempPid, NULL, producer, NULL);pthread_create(&tempCid, NULL, consumer, NULL);//回收线程pthread_join(tempPid, NULL);pthread_join(tempCid, NULL);return 0;
}//of main

2.3 信号量

使用信号量实现线程同步时,线程在访问共享资源时会根据操作类型执行如下操作:

  • 若有线程申请访问共享资源,系统会执行P操作使共享资源计数减一;
  • 若有线程释放共享资源,系统会执行V操作使共享资源计数加一。

信号量的使用也分为四个步骤:

  • (1)初始化信号量:sem_init();
  • (2)阻塞等待信号量:sem_wait();
  • (3)唤醒阻塞线程:sem_post();
  • (4)释放信号量:sem_destroy()。

2.3.1 初始化信号量

#include <pthread.h>int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化信号量。

参数说明

  • sem:指向信号量变量的指针;
  • pshared:用于控制信号量的作用范围,其取值通常为0与非0:
    – 当pshared被设置为0时,信号量将会被放在进程中所有线程可见的地址内,由进程中的线程共享;
    – 当pshared被设置为非0值时,信号量将会被放置在共享内存区域,由所有进程共享。
  • value:设置信号量sem的初值。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1,并设置errno。

2.3.2 阻塞等待信号量

#include <pthread.h>int sem_wait(sem_t *sem);

功能:阻塞等待信号量,sem_wait函数对应P操作,若调用成功,则会使信号量sem的值减一。

参数说明

  • sem:指向信号量变量的指针。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1,并设置errno。

2.3.3 唤醒阻塞线程

#include <pthread.h>int sem_post(sem_t *sem);

功能:唤醒阻塞线程,sem_post函数对应V操作,若调用成功,则会使信号量sem的值加一。

参数说明

  • sem:指向信号量变量的指针。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1,并设置errno。

2.3.4 释放信号量

#include <pthread.h>int sem_destroy(sem_t *sem);

功能:与互斥锁类似,信号量也是一种系统资源,使用完毕之后应主动回收,函数调用成功,则会使信号量sem的值加一。

参数说明

  • sem:指向信号量变量的指针。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1,并设置errno。

2.3.5 获取信号量

#include <pthread.h>int sem_getvalue(sem_t *sem, int *sval);

功能:获取系统中当前信号量的值。

参数说明

  • sem:指向信号量变量的指针;
  • sval:个传入指针,用于获取信号量的值,信号量sem的值会被存储在参数sval中。

返回值说明

  • 成功:返回0;
  • 不成功:返回-1,并设置errno。

【案例 3】实现一个模拟生产者-消费者模型,但对生产者进行限制:若容器已满,生产者不能生产,需等待消费者消费。

#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#define NUM 5 
int globQueue[NUM];                         //全局数组实现环形队列
sem_t globBlankNum, globProductNum;   		//空格子信号量, 产品信号量
void *producer(void *paraArg) {int i = 0;while (1) {sem_wait(&globBlankNum);       		//生产者将空格子数--,为0则阻塞等待globQueue[i] = rand() % 1000 + 1;   //生产一个产品printf("----Produce---%d\n", globQueue[i]);sem_post(&globProductNum);          //将产品数++i = (i + 1) % NUM;                  //借助下标实现环形sleep(rand() % 1);}//of while
}//of producer
void *consumer(void *paraArg) {int i = 0;while (1) {sem_wait(&globProductNum); 		//消费者将产品数--,为0则阻塞等待printf("-Consume---%d\t%lu\n", globQueue[i], pthread_self());globQueue[i] = 0;               //消费一个产品 sem_post(&globBlankNum);        //消费掉以后,将空格子数++i = (i + 1) % NUM;sleep(rand() % 1);}//of while
}//of consumer
int main(int paraArgc, char *paraArgv[]) {pthread_t tempPid, tempCid;sem_init(&globBlankNum, 0, NUM);    //初始化空格子信号量为5sem_init(&globProductNum, 0, 0);    //初始化产品数信号量为0pthread_create(&tempPid, NULL, producer, NULL);pthread_create(&tempCid, NULL, consumer, NULL);pthread_create(&tempCid, NULL, consumer, NULL);pthread_join(tempPid, NULL);pthread_join(tempCid, NULL);sem_destroy(&globBlankNum);sem_destroy(&globProductNum);return 0;
}//of main

2.4 小结

本部分主要讲解了Linux系统中与线程相关的知识,包括线程相关操作及线程同步,其中线程操作包括创建线程、退出线程、终止线程、回收线程等;线程同步包括互斥锁、条件变量、信号量这线程同步的三种方式。线程是Linux编程基础中非常重要的一项内容。

2.5 编程题

【1】编写一个程序,开启三个线程,第一个线程向终端输出A,第二个线程向终端输出B,第三个线程向终端输出C,每个线程打印10遍,要求输出必须按照ABC的顺序显示,如:ABCABCABC…
【2】利用线程的信号量实现互斥功能,模拟打印机。

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

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

相关文章

电脑开两个微信_电脑怎么登录两个微信

1/4下载并安装微信电脑客户端&#xff0c;保证这台电脑没有登陆微信2/4就像正常打开微信一样&#xff0c;不过不是双击&#xff0c;而是快速连点四次3/4我们可以看到有两个微信登陆界面4/4用两个不同的账号进行扫码登陆即可

Linux编程基础 7.1:套接字通信流程及编程接口

1 socket通信流程 2 socket编程接口 Linux系统中常用的socket网络编程接口有&#xff1a; socket()bind()listen()accept()connect()send()recv()close()其中connect()与send()为客户端专用接口&#xff1b;bind()、listen()、accept()及recv()为服务器端专用接口&#xff1b…

cad2016中选择全图字体怎么操作_cad教程分享CAD中如何删除顽固图层?

Autocad教程公众号&#xff0c;专注于cad教程、cad教程视频的分享&#xff0c;欢迎关注&#xff0c;下载你所需的教程资源&#xff01;如你还未关注&#xff0c;请点击文章标题下方蓝色字体的"Autocad教程"进行关注。cad教程分享-CAD中如何删除顽固图层&#xff1f;方…

div 隐藏_div的position属性

如果你想把div放到合适的位置&#xff0c;请看看这篇文章。<!-- div的position属性--><html><style>.red{height:100px;background:red;}.green{height:100px;background:green;position:relative;left:50px;top:50px;}.black{height:100px;background:black…

乔布斯在斯坦福大学演讲稿英文_西方大文豪最爱的10个英文单词,写尽人世间细腻情感!...

从小浸染在汉语中的我们&#xff0c;常被汉字的意象美震撼到&#xff0c;一字就是一世界。汉字有种无与伦比的美丽&#xff0c;寥寥数字就能营造“只可意会不可言传”的意境&#xff0c;很多人感慨英文就是一串拉丁字母&#xff0c;无法传递细腻的情感。比如很多人说像「缘分」…

web前端开发论文写作_外语论文文献引言格式—MLA Style

我们之前讨论了外语论文文献引用格式—APA Style和Chicago Style—芝加哥论文脚注引注格式&#xff0c;今天我们来介绍在Essay写作中如何使用APA格式引用文献。MLA格式是英文论文写作最常用的一种参考文献格式。很多留学小伙伴都觉得MLA引用格式很复杂&#xff0c;今天译然小编…

pmbok第七版_PMBOK第七版要来了!都有哪些变化?你准备好了么?

PMBOK第7版#PMP##职场##项目管理##战略##价值#2020年1月15日PMBOK 第7版的征求意见稿发布&#xff0c;并于2020年1月14日结束意见征集&#xff0c;预计于今年第四季度发布。基于我的项目组合管理(PfMP)&#xff0c;项目集管理(PgMP),项目管理(PMP)的培训和研究经验&#xff0c;…

简述python的特性_Python的特性概要

1、和C比较&#xff0c;Python是解释型的语言&#xff0c; 2、a>字节码特性 b>动态语义&#xff0c;即在赋值时才确定数据类型 c>缩进&#xff0c;点击tap键缩进四个空格&#xff0c;使用编译器是记得查看&#xff01;3、注意 写Python的时候首先要记得定义编码格式&a…

python自动化办公实例展示_python自动化办公?学这些就够用了

知乎上有人提问&#xff1a;用python进行办公自动化都需要学习什么知识呢&#xff1f;这可能是很多非IT职场人士面临的困惑&#xff0c;想把python用到工作中&#xff0c;却不知如何下手&#xff1f; python在自动化办公领域越来越受欢迎&#xff0c;批量处理简直是加班族的福音…

Linux编程基础 8.3:I/O多路转接服务器

1 简介 为进一步提升服务器效率&#xff0c;人们提出了一种被称为I/O多路转接的模型。其中“多路”指代连接到服务器的多个客户端程序&#xff0c;而“转接”则是指在服务器主线与各分支之间设置一个“岗位”&#xff0c;由该岗位实现监控多路连接中数据状态的功能&#xff0c…

测井储层参数预测+人工智能方法

1 问题描述 测井储层参数预测 地层泥质含量&#xff1b;地层孔隙度&#xff08;POR&#xff09;&#xff1b;含水饱和度&#xff08;SW&#xff09;&#xff1b;渗透率&#xff08;PERM&#xff09;。 输入&#xff1a;声波时差&#xff08;AC&#xff09;、补偿中子&#x…

windows 安装openssh服务端_Git神器| SourceTree安装使用教程

SourceTree 是 Windows 和Mac OS X 下免费的Git客户端管理工具。支持创建、克隆、提交、push、pull 和合并等操作。一、sourcetree的安装1. 下载sourcetree下载链接&#xff1a;Sourcetree | Free Git GUI for Mac and Windows2. 安装sourcetree点击安装&#xff0c;第一个创建…

中国大学生计算机设计大赛--软件应用与开发类--经验总结

1 大赛介绍 中国大学生计算机设计大赛是我国高校面向本科生最早的赛事之一&#xff0c;自2008年开赛至2019年&#xff0c;一直由教育部高校与计算机相关教指委等或独立或联合主办。此赛目前是全国普通高校大学生竞赛排行榜榜单赛事之一。 我们参加了很多届大赛&#xff0c;在2…

网络与系统安全1.1 中间人攻击

1 Diffie-Hellman密钥交换过程 2 中间人攻击过程

centos 安装jdk_centos7配置jdk

一、查看centos7是否有自带jdk查看是否安装过java rpm -qa | grep java 如果是centos 一般会自带两个openjdk rpm -e --nodeps 要卸载的包 (包通过上面的指令可以获取到)] 将显示java的包全都卸载 命令&#xff1a; rpm -e --nodeps 要卸载的包二、创建/opt/app目录,用于安装JD…

隐私计算 1 隐私计算的定义与背景

1 隐私计算的定义 隐私计算&#xff1a;在保证数据提供方不泄露原始数据的前提下&#xff0c;对数据进行分析计算的一系列技术&#xff0c;保障数据在流通和融合过程中“可用不可见”。 隐私计算涉及到的学科&#xff1a; 密码学&#xff1b;统计学&#xff1b;计算机体系结…

php object 对象不存在。增加对象_PHP核心

1、单例模式单例模式三要素&#xff1a;拥有一个构造函数&#xff0c;并且为private拥有一个静态成员变量来保持类的实例拥有一个访问这个实例的静态方法<?php //Instan.php 单例类class Instan{static public $instance null;private function __construct(){var_dump(…

数据库查询求小于_SQL学习笔记(二)简单查询

本篇主要学习如何通过简单查询获取想要的数据。从零学会SQL&#xff1a;简单查询​www.zhihu.com基本的查询语句从数据库中查找数据时要使用select子句&#xff0c;select子句是sql中使用最多、最基本的子句。子句是sql语句的组成要素&#xff0c;是以某一个关键字作为开始的语…

隐私计算 2.1秘密共享--问题模型及定义

1 秘密共享问题模型 1.1 富翁和三个儿子的故事 问题&#xff1a; 富翁想在自己的三个儿子中找一个最聪明的来继承自己的遗产&#xff0c;在保护好财富的同时&#xff0c;让其他两个人也参与进来&#xff0c;使得兄弟间和睦相处&#xff0c;家庭和谐。 大儿子的方案&#xff…

如何命令行结束react程序_想要成为前端Star 吗?一首歌时间将React / Vue 应用Docker 化...

前言 以前一直有疑问困扰着我&#xff1a;人人都在吹的Docker容器化&#xff0c;与前端有何关系&#xff1f;然而在近两年的编程生涯&#xff0c;在每一次产品迭代中&#xff0c;渐渐体会到了容器化其魅力所在。应用部署从刀耕火种&#xff0c;到DevOps崛起&#xff0c;原来不止…