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

Linux--线程的控制

目录

0.前言

 1.pthread库

2.关于控制线程的接口

2.1.创建线程(pthread_create)

2.2.线程等待(pthread_join)

代码示例1:

​编辑

***一些问题***

2. 3.创建多线程  

3.线程的终止 (pthread_exit  / pthread_cancel)

总结:

4.线程分离 (pthread_detach)

新线程分离主线程

5.C++ 11中的多线程


0.前言

线程的创建,终止,等待,分离


 1.pthread库

        Linux中有线程吗?没有,只有轻量级进程--(就是线程)。因此Linux下的系统调用只会给用户提供创建轻量级进程的接口,这些接口需要被pthread库进行封装,按照线程的接口提供给用户,用户通过这些接口来创建,终止,等待,分离线程。所以我们称Linux的线程为用户级线程,windows的线程为内核级线程。

 


2.关于控制线程的接口

2.1.创建线程(pthread_create)

引入接口:pthread_create,用于创建一个新线程

参数说明

  • pthread_t *thread:这是一个指向 pthread_t 类型的指针,用于存储新创建的线程的标识符。通过这个标识符,你可以引用或操作这个线程。
  • const pthread_attr_t *attr:这是一个指向 pthread_attr_t 类型的指针,用于设置线程的属性,如堆栈大小、调度策略等。如果传递 NULL,则使用默认属性。
  • void *(*start_routine) (void *):这是新线程将要执行的函数的指针。该函数必须接受一个 void * 类型的参数并返回一个 void * 类型的值。这个函数的参数 arg 将被传递给新线程。(输入一个函数的地址)
  • void *arg:这是传递给 start_routine 函数的参数。

如果成功,pthread_create 返回 0;如果失败,则返回错误码。


2.2.线程等待(pthread_join)

引入接口:pthread_join

参数说明

  • pthread_t thread:这是要等待的线程的标识符(ID),该标识符是由 pthread_create 函数返回的。
  • void **retval:这是一个指向 void * 指针的指针,用于接收被等待线程的返回值。如果被等待的线程调用了 pthread_exit 并传递了一个返回值,或者简单地返回了一个值(对于从 void* 返回类型的线程函数),那么这个值就可以通过这个参数返回给等待的线程。如果对这个返回值不感兴趣,可以传递 NULL

如果成功,pthread_join 返回 0;如果失败,则返回错误码。


代码示例1:

线程的创建和等待:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void *threadrun(void *args)
{int cnt =10;while(cnt){std::cout<<"new thread run ...,cnt: "<<cnt--<<std::endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;int n = pthread_create(&tid,nullptr,threadrun,(void*)"thread 1");std::cout<<"main thread join begin..."<<std::endl;n= pthread_join(tid,nullptr);if(n==0){std::cout<<"main thread wait success"<<std::endl;}return 0;
}

***一些问题***

问题1:mian和new线程谁先运行?不确定

问题2:我们期望谁最后退出?main thread,如何来保证呢?

join来保证,不join呢?会造成类似僵尸进程的问题

问题3:tid是什么样子的?

代码:以16进制的形式打印出来 

std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}std::string tid_str = PrintToHex(tid); // 我们按照16进行打印出来std::cout << "tid : " << tid_str << std::endl;

这个线程id是一个虚拟地址,后面再谈


问题4:全面看待线程函数传参,它可以传任意类型,当然也可以传类对象的地址,这意味着我们可以给线程传递多个参数,多种方法了

class ThreadData
{
public:std::string name;int num;
};
void *threadrun(void *args)
{//静态强转 ThreadData *td = static_cast<ThreadData*>(args);int cnt =10;while(cnt){std::cout << td->name << " run ..." <<"num is: "<<td->num<< ", cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}
int main()
{ThreadData *td=new ThreadData();td->name ="thread-1";td->num = 1;int n = pthread_create(&tid,nullptr,threadrun,(void*)&td);
}

传类对象的时候最好是在堆上开辟,这样多个线程之间就不会互相干扰。


问题5:pthread_create第三个参数的返回值,该返回值是void*类型的,如果主线程想要获取线程的返回值,可以通过join函数获取(在线程没出错的情况下是能获取到的,如果某一个线程出错,主线程也是会跟着崩掉,因为线程出错误,是直接给整个进程发信号的,导致整个进程都挂掉了)

代码示例:返回一个类对象

#include <iostream>
#include <string>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>class ThreadData
{
public:int Excute(){return x + y;}
public:std::string name;int x;int y;// other
};class ThreadResult
{
public:std::string print(){return std::to_string(x) + "+" + std::to_string(y) + "=" + std::to_string(result);}
public:int x;int y;int result;
};
std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}
void *threadRun(void *args)
{ThreadData *td = static_cast<ThreadData*>(args); // (ThreadData*)argsThreadResult *result = new ThreadResult();int cnt = 10;while(cnt){sleep(3); std::cout << td->name << " run ..." << ", cnt: " << cnt-- << std::endl;result->result = td->Excute();result->x = td->x;result->y = td->y;break;//跑一次退出}delete td;return (void*)result;
}
int main()
{pthread_t tid;ThreadData *td=new ThreadData();td->name="thread-1";td->x=10;td->y=20;int n = pthread_create(&tid, nullptr, threadRun, td);std::string tid_str = PrintToHex(tid); // 我们按照16进行打印出来std::cout << "tid : " << tid_str << std::endl;std::cout<<"main thread join begin..."<<std::endl;ThreadResult *result = nullptr; // 开辟了空间的!!!n = pthread_join(tid, (void**)&result); if(n == 0){std::cout << "main thread wait success, new thread exit code: " << result->print() << std::endl;}sleep(10);return 0;
}


2. 3.创建多线程  

下面是一段示例:

初步:创建线程id和线程name,保存所有线程的id信息,最后主线程回收每个线程

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>const int num = 10;
std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *threadrun(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << name << " is running" << std::endl;sleep(1);break;}return args;
}
int main()
{std::vector<pthread_t> tids;for(int i = 0; i < num; i++){// 1. 有线程的idpthread_t tid;// 2. 线程的名字char *name = new char[128];snprintf(name, 128, "thread-%d", i+1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);//3.保存所有线程idtids.push_back(tid);}for(auto tid:tids){void*name=nullptr;pthread_join(tid,&name);std::cout<<(const char*)name<<"quit..."<<std::endl;delete (const char*)name;}}
​

我们用vector储存线程id集


3.线程的终止 (pthread_exit  / pthread_cancel)

        对于新线程来说,线程终止,函数return;main函数结束,主线程结束,表示整个进程结束!

        关于exit:专门用来终止进程的,不能用来终止线程!任意一个线程调用exit都表示进程终止!如果你想让一个线程马上终止,这里就要用到第三个接口:pthread_exit

参数:

  • retval:这是一个指向任意数据的指针,该数据将被线程的终止状态所使用,并且可以被其他线程通过调用 pthread_join 来访问。

当然你还可以使用接口:pthread_cancel取消一个线程

参数:

  • thread:要发送取消请求的线程标识符(pthread_t 类型)。

代码示例:

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>const int num = 10;
std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *threadrun(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << name << " is running" << std::endl;sleep(1);break;}//return args;pthread_exit(args);
}
int main()
{std::vector<pthread_t> tids;for(int i = 0; i < num; i++){// 1. 有线程的idpthread_t tid;// 2. 线程的名字char *name = new char[128];snprintf(name, 128, "thread-%d", i+1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);//3.保存所有线程idtids.push_back(tid);}for(auto tid:tids){void*name=nullptr;pthread_join(tid,&name);std::cout<<(const char*)name<<"quit..."<<std::endl;delete (const char*)name;}sleep(100);
}

在主线程未退出的情况下,其它线程成功退出了。


线程取消,退出结果为-1; #define PTHREAD_CANCELED ((void *) -1)

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>const int num = 10;
std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *threadrun(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << name << " is running" << std::endl;sleep(1);}
}
int main()
{std::vector<pthread_t> tids;for(int i = 0; i < num; i++){// 1. 有线程的idpthread_t tid;// 2. 线程的名字char *name = new char[128];snprintf(name, 128, "thread-%d", i+1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);//3.保存所有线程idtids.push_back(tid);}sleep(5);for(auto tid : tids){pthread_cancel(tid); // 取消std::cout << "cancel: " << PrintToHex(tid) << std::endl;void *result = nullptr; // 线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)pthread_join(tid, &result);std::cout << (long long int)result << " quit..." << std::endl;}sleep(100);
}


总结:

    新线程如何终止?

    1. 线程函数 return

    2. pthread_exit

    3. main thread call pthread_cancel, 新线程退出结果是-1


4.线程分离 (pthread_detach)

        线程分离的是将线程与创建它的进程(或主线程)的终止状态分离。当一个线程被分离后,它依然属于进程内部,但它不再需要被其他线程显式地等待(通过 pthread_join)来释放其资源。当分离的线程终止时,它的所有资源会自动被释放回系统,无需其他线程的干预。

参数

  • thread:要分离的线程的标识符(pthread_t 类型)。

返回值

  • 成功时返回 0。
  • 失败时返回错误号。

        一个线程被创建,默认是joinable,必须要被join的;如果一个线程被分离,线程的工作状态分离状态,不需要/不能被join的。

        这里我们还需要借助一个接口:pthread_self,一调用就是获取自己的线程id


新线程分离主线程

代码示例:一旦分离主线程就不能等待了,如果等待会发生什么?这里我们看一下分离且join后,join的返回值

        我们发现返回值为:22,这说明主线程以等待就直接出错了。所以主线程无需等待,主线程可以做自己的事情了。如果在线程分离的情况下,且主线程没有做等待,新线程出错了,整个进程也是直接挂掉的,因为它还是在进程内部。

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>const int num = 10;
std::string PrintToHex(pthread_t &tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *threadrun(void *args)
{pthread_detach(pthread_self());std::string name = static_cast<const char*>(args);while(true){std::cout << name << " is running" << std::endl;sleep(1);}pthread_exit(args);
}
int main()
{std::vector<pthread_t> tids;for(int i = 0; i < num; i++){// 1. 有线程的idpthread_t tid;// 2. 线程的名字char *name = new char[128];snprintf(name, 128, "thread-%d", i+1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);//3.保存所有线程idtids.push_back(tid);}sleep(5);for(auto tid : tids){std::cout << "cancel: " << PrintToHex(tid) << std::endl;void *result = nullptr; // 线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)int n = pthread_join(tid, &result);std::cout << (long long int)result << " quit...n :" <<n<< std::endl;}sleep(100);
}


5.C++ 11中的多线程

        C++11在Linux中使用多线程,编译时也是要链接pthread库,因为C++11中的多线程本质,就是对原生线程库接口的封装!!!

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>void threadrun(std::string name, int num)
{while(num){std::cout << name << " num : " << num<< std::endl;num--;sleep(1);}
}int main()
{std::string name = "thread-1";std::thread mythread(threadrun, std::move(name), 10);while(true){std::cout << "main thhread..." << std::endl;sleep(1);}mythread.join();return 0;
}

 

 

相关文章:

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…...

大数据------JavaWeb------会话跟踪技术(Cookie、Session)(完整知识点汇总)

会话跟踪技术&#xff08;Cookie&Session&#xff09; 注意&#xff1a; HTTP协议是无状态 的&#xff0c;即每次浏览器向服务器请求时&#xff0c;服务器都会将该请求视为新的请求&#xff0c;因此我们需要会话跟踪技术来实现会话内的数据共享 会话 当用户打开浏览器&am…...

crossJoin笛卡尔积

crossJoin笛卡尔积 在Spark中&#xff0c;crossJoin方法用于执行两个数据集之间的笛卡尔积操作。具体来说&#xff0c;如果有两个数据集&#xff08;DataFrame或Dataset&#xff09;&#xff0c;调用crossJoin方法将会生成一个新的数据集&#xff0c;其中包含两个原始数据集中所…...

Java客户端调用SOAP方式的WebService服务实现方式分析

简介 在多系统交互中&#xff0c;有时候需要以Java作为客户端来调用SOAP方式的WebService服务&#xff0c;本文通过分析不同的调用方式&#xff0c;以Demo的形式&#xff0c;帮助读者在生产实践中选择合适的调用方式。 本文JDK环境为JDK17。 结论 推荐使用Axis2或者Jaxws&#…...

华为机试真题--字符串序列判定

题目描述: 输入两个字符串S和L,都只包含英文小写字母,其中S长度<=100,L长度<=500000,请判定S是否是L的有效字串。 判定规则: S中的每个字符在L中都能找到(可以不连续),且S在L中字符的前后顺序与S中顺序要保持一致。(例如,S="ace"是L="abcd…...

Linux内核 -- 虚拟化之virtqueue结构

Linux Kernel中的Virtqueue Virtqueue是Linux Kernel中用于实现Virtio设备的一个关键数据结构。Virtio是一种虚拟I/O设备标准&#xff0c;旨在简化虚拟化环境中虚拟设备与虚拟机之间的通信。Virtqueue则是实现这种通信的核心机制。以下是Virtqueue的一些关键点&#xff1a; V…...

【pytorch18】Logistic Regression

回忆线性回归 for continuous:y xwbfor probability output:yσ(xwb) σ:sigmoid or logistic 线性回归是简单的线性模型&#xff0c;输入是x&#xff0c;网络参数是w和b&#xff0c;输出是连续的y的值 如何把它转化为分类问题?加了sigmoid函数&#xff0c;输出的值不再是…...

PostgreSQL的使用

PostgreSQL的使用 1.首先&#xff0c;使用docker进行安装pgvector数据库&#xff0c;具体的安装步骤可以查看我之前发的博文。 2.docker exec -it pgvector /bin/bash 进入docker容器内部&#xff0c;操作数据库&#xff0c;上述命令是以交互式命令进入了容器的内部&#xf…...

python 高级技巧 0706

python 33个高级用法技巧 列表推导式 简化了基于现有列表创建新列表的过程。 squares [x**2 for x in range(10)] print(squares)[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]字典推导式 用简洁的方式创建字典。 square_dict {x: x**2 for x in range(10)} print(square_dict){0…...

面试经典 106. 从中序与后序遍历序列构造二叉树

最近小胖开始找工作了&#xff0c;又来刷苦逼的算法了 555 废话不多说&#xff0c;看这一题&#xff0c;上链接&#xff1a;https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/?envTypestudy-plan-v2&envIdtop-inte…...

如何解决群晖Docker注册表查询失败/无法拉取镜像等问题

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 问题概述 📒📒 解决方案 📒🔖 方法一🔖 方法二🔖 方法三⚓️ 相关链接 🚓️📖 介绍 📖 在群晖(Synology)NAS设备上使用Docker时,我们可能会遇到查询Docker注册表失败,无法拉取Docker镜像的问题。这种情况…...

【Scrapy】 深入了解 Scrapy 中间件中的 process_spider_input 方法

准我快乐地重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 再去做没流着情泪的伊人 假装再有从前演过的戏份 重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 你纵是未明白仍夜深一人 穿起你那无言毛衣当跟你接近 &#x1f3b5; 陈慧娴《傻女》 Scrapy 是…...

数据库MySQL---基础篇

存储和管理数据的仓库 MySQL概述 数据库相关概念 数据库&#xff08;DataBase&#xff09;---数据存储的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统&#xff08;DBMS&#xff09;-----操纵和管理数据库的大型软件 SQL----操作关系型数据库的编程语言&#xff…...

欧姆龙安全PLC及周边产品要点指南

电气安全、自动化设备作业安全&#xff0c;向来是非常非常之重要的&#xff01;越来越多的客户在规划新产线、改造既有产线的过程中&#xff0c;明确要求设计方和施工方将安全考虑进整体方案中进行考虑和报价&#xff01;作为一名自动化电气工程师&#xff0c;尤其是高级工程师…...

tableau气泡图与词云图绘制 - 8

气泡图及词云图绘制 1. 气泡图绘制1.1 选择相关属性字段1.2 选择气泡图1.3 设置颜色1.4 设置标签1.5 设置单位 2. 气泡图绘制 - 22.1 类别筛选2.2 页面年份获取2.3 行列获取2.4 历史轨迹显示 3. 词云图绘制3.1 筛选器3.2 选择相关属性3.3 选择气泡图3.4 设置类型颜色3.5 设置形…...

C语言 找出一个二维数组中的鞍点

找出一个二维数组中的鞍点,即该位置上的元素在该行上最大、在该列上最小。也可能没有鞍点。 #include <stdio.h>int main() {int matrix[4][4] {{10, 17, 13, 28},{21, 14, 16, 40},{30, 42, 23, 39},{24, 11, 19, 17}};int n 4, m 4;int found 0;for (int i 0; i …...

【笔记】在linux中设置错文件如何重置

以mysql的auto.cnf文件为例...

DNS中的CNAME与A记录:为什么无法共存A解析和C解析?

在互联网的世界中&#xff0c;DNS&#xff08;域名系统&#xff09;扮演着至关重要的角色&#xff0c;它将易于记忆的域名转换为计算机可识别的IP地址。在这个过程中&#xff0c;两种常见的DNS记录类型——CNAME记录和A记录——经常被提及。然而&#xff0c;它们之间存在着一些…...

线程和进程

文章目录 进程和线程进程线程案例 时间片概念调度方式线程的创建和启动第一种创建方式第二种创建方式&#xff08;匿名内部类&#xff09;第三种创建方式&#xff08;Runnable接口&#xff09;main线程和t线程之间的关系 线程的名字线程的优先级线程状态 进程和线程 进程 在计…...

【JavaEE】 简单认识CPU

&#x1f435;本篇文章将对cpu的相关知识进行讲解 一、认识CPU 下图是简略的冯诺依曼体系结构图 上图中&#xff0c;存储器用来存储数据&#xff0c;注意在存储器中都是以二进制的形式存储数据的&#xff0c;CPU就是中央处理器&#xff0c;其功能主要是进行各种算术运算和各种…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...