【Linux】线程的创建、回收分离以及线程的同步互斥

一、多线程的基本编程

线程回收:线程在运行时需要分配内存空间、处理器时间等系统资源,这些资源在线程结束后应当被释放,以便其他线程或进程能够继续使用它们。如果不回收线程,就会导致系统资源的浪费和资源泄漏问题。

1.join回收(常用)

使用 pthread_join 函数可以实现线程的等待和回收效果。具体来说,调用 pthread_join 函数会使当前线程(通常是主线程)等待指定的线程结束执行,然后将其资源回收。

当调用 pthread_join 后,主线程会阻塞直到指定的线程结束。这意味着主线程会等待被等待线程执行完成后再继续执行后续的代码。如果不使用 pthread_join,主线程可能会在被等待线程执行完之前就结束,这样被等待线程的资源可能无法得到回收造成僵尸线程,可能导致资源泄漏。

因此,使用 pthread_join 函数可以确保线程的资源在合适的时候被回收,保证程序的正确性和稳定性。

  1. pthread_create: 这个函数用于创建一个新线程。它接受四个参数:第一个参数是指向线程标识符的指针,用于存储新线程的标识符;第二个参数是用于设置线程属性的指针,通常为 NULL,表示使用默认属性;第三个参数是一个函数指针,指向我们想要在线程中执行的函数;第四个参数是传递给线程函数的参数。

  2. pthread_join: 这个函数用于等待指定的线程结束执行。它接受两个参数:第一个参数是要等待的线程的标识符;第二个参数是一个指向线程返回值的指针。在这个示例中,我们传递了 NULL,表示我们不关心线程的返回值。

  3. pthread_exit: 这个函数用于终止调用它的线程。在线程函数中,当线程完成任务后,应该使用 pthread_exit 来终止线程。

  4. 通常情况下,线程在结束时会调用 pthread_exit 来主动结束自己的执行,并且可以通过传递一个退出状态来向其他线程传递信息。然后,其他线程可以使用 pthread_join 来等待该线程的结束,并获取它的退出状态。所以当一个线程调用 pthread_join 来等待另一个线程结束时,被等待的线程可以通过 pthread_exit 函数传递一个退出状态,这个退出状态会被 pthread_join 函数接收到,并存储在指定的变量中。这样,主线程或者其他等待线程就可以获取到被等待线程的返回值。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{char *message = (char *)ptr;while(1){printf("%s\n", message);sleep(1);}pthread_exit(NULL);
}int main() 
{pthread_t thread1, thread2;char *message1 = "Thread 1: Hello, world!";char *message2 = "Thread 2: Goodbye, world!";// 创建线程1if (pthread_create(&thread1, NULL, print_message, (void *)message1) != 0) {fprintf(stderr, "Error creating thread 1\n");return 1;}// 创建线程2if (pthread_create(&thread2, NULL, print_message, (void *)message2) != 0) {fprintf(stderr, "Error creating thread 2\n");return 1;}// 等待线程1结束if (pthread_join(thread1, NULL) != 0) {fprintf(stderr, "Error joining thread 1\n");return 1;}// 等待线程2结束if (pthread_join(thread2, NULL) != 0) {fprintf(stderr, "Error joining thread 2\n");return 1;}return 0;
}

2. 也可以通过调用 pthread_detach 函数将线程标记为“分离”的。这样,线程在结束时会自动释放资源,而不需要显式地调用 pthread_join 来等待和回收。分离线程一般用于不需要等待其完成的情况,比如后台任务。

调用 pthread_detach 后,即使主线程结束,被分离的线程仍然会继续执行,直到它结束或者被取消。分离状态的线程无法被其他线程等待和回收,因此它们在后台执行,并且不会产生僵尸线程。

需要注意的是,一旦线程被设置为分离状态,就不能再将其恢复为非分离状态。因此,在调用 pthread_detach 之前,需要确保这个线程不再需要被其他线程等待和回收。

但是如果主线程退出了,分离线程也会被终止。整个进程的生命周期是由主线程控制的。当主线程退出时,整个进程会被终止,这也包括所有的分离线程。

分离线程的主要特点是,它们不会阻止进程的终止,即使它们仍在执行。当主线程结束时,系统会自动回收分离线程的资源,并终止它们的执行。因此,分离线程通常用于执行一些不需要主线程等待的后台任务,但在主线程退出时,它们也会被终止。所以使用 pthread_detach 分离线程的一个潜在问题是,如果主线程在分离线程任务开始执行之前就结束了,那么分离线程可能没有机会完成它的任务。这种情况下,分离线程会被立即终止,而不会执行任何任务。

因此,在使用 pthread_detach 分离线程时,需要确保主线程不会过早退出,以允许分离线程有足够的时间来执行其任务。否则,可以考虑其他方式来管理线程,例如使用非分离线程或者适当地设计主线程的生命周期。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{char *message = (char *)ptr;printf("%s\n", message);return NULL;
}int main() {pthread_t thread1, thread2;char *message1 = "Thread 1: Hello, world!";char *message2 = "Thread 2: Goodbye, world!";// 创建线程1并设置为分离状态if (pthread_create(&thread1, NULL, print_message, (void *)message1) != 0) {fprintf(stderr, "Error creating thread 1\n");return 1;}pthread_detach(thread1);// 创建线程2并设置为分离状态if (pthread_create(&thread2, NULL, print_message, (void *)message2) != 0) {fprintf(stderr, "Error creating thread 2\n");return 1;}pthread_detach(thread2);// 主线程可以继续执行其他操作,不需要等待分离线程结束// 睡眠一段时间以确保分离线程有足够的时间打印信息sleep(1);return 0;
}

3.pthread_attr_destroy(不常用,看具体场景使用)

使用pthread_attr_destroy 函数来销毁线程属性对象 attr。这个函数的作用是销毁先前通过 pthread_attr_init 初始化的线程属性对象,释放其所占用的资源。

在示例中,我们在创建线程前初始化了一个线程属性对象 attr,然后通过 pthread_attr_setdetachstate 设置了线程的分离状态。在设置完线程属性后,我们调用了 pthread_attr_destroy(&attr) 来销毁这个线程属性对象,因为在后续的代码中不再需要它了。

总的来说,pthread_attr_destroy 函数用于释放不再需要的线程属性对象,以避免资源泄漏。

pthread_attr_destroy 函数通常在需要动态创建和销毁线程属性对象时使用。它的使用频率相对较低,因为大多数情况下,线程属性对象是在函数内部或者全局定义的静态对象,不需要销毁。

int pthread_attr_destroy(pthread_attr_t *attr);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{char *message = (char *)ptr;printf("%s\n", message);return NULL;
}int main() {pthread_t thread1, thread2;char *message1 = "Thread 1: Hello, world!";char *message2 = "Thread 2: Goodbye, world!";// 创建线程1并设置为分离状态pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);if (pthread_create(&thread1, &attr, print_message, (void *)message1) != 0) {fprintf(stderr, "Error creating thread 1\n");return 1;}// 创建线程2并设置为分离状态if (pthread_create(&thread2, &attr, print_message, (void *)message2) != 0) {fprintf(stderr, "Error creating thread 2\n");return 1;}// 不再需要 attr,可以销毁pthread_attr_destroy(&attr);// 主线程可以继续执行其他操作,不需要等待分离线程结束// 睡眠一段时间以确保分离线程有足够的时间打印信息sleep(1);return 0;
}

 

二、线程安全(同步互斥)

1.使用互斥锁达到互斥

互斥锁(Mutex)是一种用于多线程编程的同步机制,用于保护共享资源免受并发访问的影响。互斥锁可以确保在任何时刻,只有一个线程可以访问被保护的资源,从而避免了竞争条件(Race Condition)的发生。

想象一下你和朋友在玩一个游戏,这个游戏有一把钥匙,你们两个需要轮流使用这把钥匙。问题是,如果你们两个同时试图拿起钥匙,可能会发生什么?或者当一个人正在使用钥匙的时候,另一个人也想拿走钥匙会发生什么?

这个时候,互斥锁就像是一把只有一个人能够拿起的钥匙。当你需要使用这个共享资源(比如说一个全局变量、一个文件等)时,你会先尝试去获取这把互斥锁(也就是拿起钥匙)。如果没有其他人正在使用它,你就可以顺利地获取到互斥锁(拿到钥匙),然后就可以安全地使用这个共享资源了。一旦你使用完毕,你会把互斥锁(钥匙)放回原处,让其他人可以继续使用。

但是,如果另一个人已经拿着这把互斥锁(钥匙),那么你就需要等待,直到他释放出互斥锁。这就保证了在同一时刻,只有一个线程可以访问共享资源,避免了数据的混乱和不一致性。

使用流程:

初始化互斥锁

在使用互斥锁之前,需要先初始化互斥锁。可以使用 pthread_mutex_init 函数进行初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

加锁(Lock)

在访问共享资源之前,线程需要先尝试获取互斥锁的所有权,这称为加锁。只有成功获取锁的线程才能访问共享资源。可以使用 pthread_mutex_lock 函数来加锁

pthread_mutex_lock(&mutex);
// 访问共享资源
pthread_mutex_unlock(&mutex);

解锁(Unlock)

在线程完成对共享资源的访问后,需要释放互斥锁,以允许其他线程访问共享资源。可以使用 pthread_mutex_unlock 函数来解锁:

pthread_mutex_unlock(&mutex);

销毁互斥锁

在不再需要互斥锁时,需要将其销毁以释放相关资源。可以使用 pthread_mutex_destroy 函数来销毁互斥锁:

pthread_mutex_destroy(&mutex);

注意事项

  • 加锁和解锁必须成对出现,否则可能导致死锁或其他问题。
  • 加锁的线程必须记得在不再需要共享资源时释放锁,否则可能导致其他线程无法访问共享资源。
  • 尽量避免在加锁期间执行耗时操作,以减少锁的持有时间,提高并发性能。

通过合理地使用互斥锁,可以确保多线程程序中的共享资源得到正确地访问和修改,避免了竞争条件和数据不一致的问题。

 

补充:尝试加锁

如果一个线程尝试获取锁但失败了(因为锁已经被其他线程获取了),它可以选择阻塞等待也可以尝试获取锁并立即返回结果。后者称为尝试加锁,可以使用 pthread_mutex_trylock 函数来尝试加锁。

pthread_mutex_trylock 函数提供了一种非阻塞的方式来尝试获取互斥锁。当线程调用 pthread_mutex_trylock 尝试获取锁时,如果互斥锁当前已经被其他线程占用,那么该函数会立即返回,并返回一个非零值,表示获取锁失败。线程可以根据这个返回值来判断是否获取了锁。

这种方式的优势在于,如果线程在尝试获取锁时不希望被阻塞,可以使用 pthread_mutex_trylock 函数,它会立即返回,线程可以继续执行其他操作。同时,通过不断地尝试获取锁,线程可以在后续的尝试中成功地获取到锁,而不需要一直等待锁的释放。

与之相对应的是上面pthread_mutex_lock 函数,它会在锁被其他线程占用时阻塞当前线程,直到获取到锁为止。pthread_mutex_lock适用于那些必须立即获取锁才能继续执行的情况。

if (pthread_mutex_trylock(&mutex) == 0)

{ // 成功获取锁,访问共享资源

pthread_mutex_unlock(&mutex);

}

else

{ // 锁被其他线程占用,执行其他操作

}

(1)基于join回收的互斥锁

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>#define NUM_THREADS 5pthread_mutex_t mutex;
int counter = 0;void *thread_function(void *arg) 
{pthread_mutex_lock(&mutex);printf("Thread %ld: Counter = %d\n", (long)arg, ++counter);pthread_mutex_unlock(&mutex);pthread_exit(NULL);
}int main() 
{pthread_t threads[NUM_THREADS];int i;pthread_mutex_init(&mutex, NULL);for (i = 0; i < NUM_THREADS; i++) {if (pthread_create(&threads[i], NULL, thread_function, (void *)i) != 0) {fprintf(stderr, "Error creating thread %d\n", i);return 1;}}for (i = 0; i < NUM_THREADS; i++) {if (pthread_join(threads[i], NULL) != 0) {fprintf(stderr, "Error joining thread %d\n", i);return 1;}}pthread_mutex_destroy(&mutex);return 0;
}

(2)基于使用 pthread_detach 分离回收的互斥锁

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>#define NUM_THREADS 5pthread_mutex_t mutex;
int counter = 0;void *thread_function(void *arg) 
{pthread_mutex_lock(&mutex);printf("Thread %ld: Counter = %d\n", (long)arg, ++counter);pthread_mutex_unlock(&mutex);pthread_exit(NULL);
}int main() {pthread_t threads[NUM_THREADS];int i;pthread_mutex_init(&mutex, NULL);for (i = 0; i < NUM_THREADS; i++) {if (pthread_create(&threads[i], NULL, thread_function, (void *)i) != 0) {fprintf(stderr, "Error creating thread %d\n", i);return 1;}}for (i = 0; i < NUM_THREADS; i++) {pthread_detach(threads[i]);}// 主线程等待一段时间,以确保分离线程有足够的时间来执行sleep(1);pthread_mutex_destroy(&mutex);return 0;
}

2.使用互斥锁和条件变量达到线程间的同步互斥

条件变量是一种用于线程间通信的同步机制,主要用于在线程等待某个条件满足时进行阻塞,以避免忙等待,从而提高系统效率。条件变量通常与互斥锁配合使用,用于保护共享资源的访问,并在共享资源状态发生变化时通知等待的线程。

  1. 避免忙等待: 在某些情况下,线程需要等待某个条件变为真,但不希望采用忙等待的方式不断地检查条件是否满足,以免占用过多的 CPU 资源。条件变量可以让线程在条件不满足时进入阻塞状态,直到条件被满足时才唤醒线程。

  2. 提高系统效率: 使用条件变量可以降低系统资源的消耗,因为线程在等待条件变量时会进入休眠状态,不再占用 CPU 资源,从而提高了系统的整体效率。

下面是一个基于互斥锁和条件变量的多线程示例,其中线程 A 负责生产数据,线程 B 负责消费数据:

在这个示例中,两个线程共享一个缓冲区 bufferproducer 线程负责向缓冲区中生产数据,consumer 线程负责消费数据。通过互斥锁 mutex 和条件变量 cond 来保护缓冲区的访问,当缓冲区为空时,consumer 线程会进入等待状态,直到有数据被生产出来;当缓冲区已满时,producer 线程会进入等待状态,直到有数据被消费掉。

这就是典型的生产者-消费者模型,其中消费者在缓冲区为空时会阻塞等待生产者的通知,而生产者在缓冲区已满时会阻塞等待消费者的通知。这种通过条件变量和互斥锁实现的同步机制,能够确保生产者和消费者之间的协调与同步,有效地避免了竞态条件和数据不一致的问题。

其实只也可以生产者一直生产,生产好了通知消费者,消费者不用通知生产者。消费者可以不需要通知生产者,因为生产者一直在生产数据。在这种情况下,消费者只需要等待生产者通知,当生产者生产好数据时,通知消费者进行消费即可。这种模型更符合实际生产消费的场景,因为生产者负责生产,而消费者负责消费,双方各司其职,相互协作。下面这个例子是使用相互通知的,具体场景需要自己灵活选择

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>#define BUFFER_SIZE 10pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer[BUFFER_SIZE];
int count = 0;void *producer(void *arg) 
{int data = 0;while (1) {pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond, &mutex);}buffer[count++] = data++;printf("Produced: %d\n", data);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);usleep(500000); // 模拟生产数据的耗时操作}pthread_exit(NULL);
}void *consumer(void *arg) 
{while (1) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond, &mutex);}int data = buffer[--count];printf("Consumed: %d\n", data);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);usleep(800000); // 模拟消费数据的耗时操作}pthread_exit(NULL);
}int main() 
{pthread_t producer_thread, consumer_thread;pthread_create(&producer_thread, NULL, producer, NULL);pthread_create(&consumer_thread, NULL, consumer, NULL);pthread_join(producer_thread, NULL);pthread_join(consumer_thread, NULL);return 0;
}

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

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

相关文章

Unity涂鸦纹理实现

文章目录 前言实现过程UV坐标和UI坐标对齐修改像素代码 前言 心血来潮实现下场景中提供一张纹理进行涂鸦的功能。 最终实现效果: 实现过程 UV坐标和UI坐标对齐 这里的纹理使用了UGUI的Canvas进行显示&#xff0c;所以这里使用一张RawImage。 因为Unity的视口坐标是以左下角…

Postgresql源码(127)投影ExecProject的表达式执行分析

无论是投影还是别的计算&#xff0c;表达式执行的入口和计算逻辑都是统一的&#xff0c;这里已投影为分析表达式执行的流程。 1 投影函数 用例 create table t1(i int primary key, j int, k int); insert into t1 select i, i % 10, i % 100 from generate_series(1,1000000…

STM32利用硬件I2C读取MPU6050陀螺仪数据

有了前面的基本配置&#xff0c;这节读取MPU6050的数据还算是简单&#xff0c;主要就是初始化时给MPU6050一些配置&#xff0c;取消睡眠模式&#xff0c;MPU6050开机是默认睡眠模式的&#xff0c;读写无效&#xff0c;所以上来就要先更改配置&#xff1a; MPU6050寄存器初始化…

Transformer算法组件详解

自2017年Google推出Transformer以来&#xff0c;基于其架构的语言模型便如雨后春笋般涌现&#xff0c;其中Bert、T5等备受瞩目&#xff0c;而近期风靡全球的大模型ChatGPT和LLaMa更是大放异彩。网络上关于Transformer的解析文章非常大。 前言 Transformer是谷歌在2017年的论文…

【webrtc】MessageHandler 7: 基于线程的消息处理:切换main线程向observer发出通知

以当前线程作为main线程 RemoteAudioSource 作为一个handler 仅实现一个退出清理的功能 首先on message的处理会切换到main 线程 :main_thread_其次,这里在main 线程对sink_ 做清理再次,在main 线程做出状态改变,并能通知给所有的observer 做出on changed 行为。对接mediac…

AC+AP三层组网实验(华为)

一&#xff0c;技术简介 APAC架构是一种常见的无线局域网&#xff08;WLAN&#xff09;组网方式&#xff0c;主要由接入点&#xff08;Access Point&#xff0c;简称AP&#xff09;和接入控制器&#xff08;Access Controller&#xff0c;简称AC&#xff09;组成。 在APAC架构…

AI大模型系列:自然语言处理,从规则到统计的演变

AI大模型系列文章目录 文明基石&#xff0c;文字与数字的起源与演变自然语言处理&#xff0c;从规则到统计的演变AI魔法师&#xff0c;提示工程的力量 自然语言处理&#xff0c;从规则到统计的演变 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&…

Element UI 简介

Element UI是一个基于Vue.js的组件库&#xff0c;提供了一套丰富的可复用的组件&#xff0c;包括按钮、表单、弹框、表格、菜单等等。它的设计风格简洁大方&#xff0c;易于使用&#xff0c;能够帮助开发者快速构建现代化的Web应用。 在Element UI中&#xff0c;有许多常用的组…

前端 CSS

目录 选择器 复合选择器 伪类-超链接 结构伪装选择器 伪元素选择器 画盒子 字体属性 CSS三大属性 Emmet写法 背景属性 显示模式 盒子模型 盒子模型-组成 盒子模型-向外溢出 盒子模型-圆角 盒子模型-阴影 flex position定位 CSS小精灵 字体图标 垂直对齐方式…

数据库(MySQL)—— DML语句

数据库&#xff08;MySQL&#xff09;—— DML语句 什么是DML语句添加数据给全部字段添加数据批量添加数据 修改数据删除数据 什么是DML语句 在MySQL中&#xff0c;DML&#xff08;Data Manipulation Language&#xff0c;数据操纵语言&#xff09;语句主要用于对数据库中的数…

基础安全:CSRF攻击原理与防范

CSRF的概念 CSRF(Cross-Site Request Forgery)中文名为“跨站请求伪造”。这是一种常见的网络攻击手段,攻击者通过构造恶意请求,诱骗已登录的合法用户在不知情的情况下执行非本意的操作。这种攻击方式利用了Web应用程序中用户身份验证的漏洞,即浏览器在用户完成登录后会自…

eclipse导入工程提示Project has no explicit encoding set

eclipse导入工程提示Project has no explicit encoding set 文章目录 eclipse导入工程提示Project has no explicit encoding set一、Eclipse的工程导入二、可能的问题1.在工程名下有黄色叹号 一、Eclipse的工程导入 用Eclipse的导入可以将原有工程导入到新环境中 具体方法是&…

3.C++动态内存管理(超全)

目录 1 .C/C 内存分布 2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 3. C内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 3.3 operator new函数 3.4 定位new表达式(placement-new) &#xff08;了解&#xff09; 4. 常…

工具类,包含线程池,excel图片处理

一、线程池 public class ThreadPool {/*** 核心线程*/public static final int CORE_POOL_SIZE Runtime.getRuntime().availableProcessors() 1;/*** 线程池最大线程数*/public static final int MAX_POOL_SIZE CORE_POOL_SIZE * 2;/*** 空闲线程回收时间*/public static …

Linux搭建靶场

提前准备&#xff1a; 文章中所使用到的Linux系统&#xff1a;Ubantu20.4sqlilabs靶场下载地址&#xff1a;GitHub - Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based. 一. 安装phpstudy phpstudy安装命令&#xff1a;wget -O install.sh h…

C++入门学习笔记

目录转到 -> [[…/目录|目录]] 程序结构 结构 int main() {return 0; }输出 cout << "Hello World!"; // echo:Hello World!注释 // 一行注释/* 一段注释 我也是注释 */变量 数据类型 整型 类型存储大小值范围char1 字节-128 到 127 或 0 到 255unsi…

k8s crd inferenceservices.serving.kserve.io

背景 ArgoCD无法连接到k8s集群 日志如下&#xff1a; Failed to load live state: failed to get cluster info for "https://kubernetes.default.svc": error synchronizing cache state : failed to sync cluster https://10.233.0.1:443: failed to load initia…

MongoDB聚合运算符:$slice

MongoDB聚合运算符&#xff1a;$slice 文章目录 MongoDB聚合运算符&#xff1a;$slice语法参数说明 使用举例 $slice聚合运算符返回数组的子集。 语法 $slice有两种使用语法&#xff1a; 从数组的开头或结尾返回元素&#xff1a; { $slice: [ <array>, <n> ] }从数…

增强大模型高效检索:基于LlamaIndex ,构建一个轻量级带有记忆的 ColBERT 检索 Agent

在自然语言处理领域&#xff0c;高效检索相关信息的能力至关重要。将对话式记忆集成到文档检索系统中已经成为增强信息检索代理效果的强大技术。 在文中&#xff0c;我们专为 LlamaIndex 量身定制&#xff0c;将深入探讨构建一个轻量级的带有记忆的 ColBERT 检索代理&#xff…

后端方案设计文档结构模板可参考

文章目录 1 方案设计文档整体结构2 方案详细设计2.1 概要设计2.2 详细设计方案2.2.1 需求分析2.2.2 业务流程设计2.2.3 抽象类&#xff1a;实体对象建模2.2.4 接口设计2.2.5 存储设计 1 方案设计文档整体结构 一&#xff0c;现状&#xff1a;把项目的基本情况和背景都说清楚&a…