1.前言:
对于线程和进程而言,直接广泛的认为进程是线程的基础。每一个进程都可以有很多个线程。线程与线程间是并行运行的。那么在我们开发的过程中,由于cpu的能力有限,如何来分配这个算力是我们需要考虑的。我很喜欢的一个说法是大量使用线程对于一个项目,可以直接理解成用空间去换取时间。
在 Linux 操作系统中,线程和进程是两个核心概念,是实现多任务并发执行的基础。理解它们对于掌握 Linux 的多任务处理和并发编程至关重要。接下来我将详细介绍进程和线程的定义、区别、创建和管理,以及在实际编程中的应用示例和通信方法。
2.进程:
2.1.进程的概念:
进程是一个正在执行的程序实例,它拥有自己独立的地址空间、内存、数据和文件描述符。进程是操作系统进行资源分配和任务管理的基本单位。
在Linux系统中,进程是操作系统资源分配和调度的基本单位。进程之间相互独立,一个进程的失败通常不会影响到其他进程。进程的生命周期从创建(fork
或exec
)开始,到终止(自愿退出或被杀死)结束。
进程可以通过fork()
系统调用创建新进程,新进程是父进程的一个副本,拥有与父进程相同的环境和状态,但拥有自己的pid以及
独立的执行序列。
2.2.进程的生命周期:
一个进程的生命周期包括以下几个阶段:
1.创建:使用fork()或者exec()指令来创建进程
2.运行:执行所编辑的代码。
3.阻塞:进程等待某些条件的完成(i/o操作、fifio等等)
4.唤醒:进程从阻塞态返回到运行态。
5.终止:进程完成任务或者被强制终止。
2.3.进程的创建和管理:
这里在 Linux 中,可以使用 fork()
系统调用创建新进程。fork()
创建一个子进程,子进程是父进程的副本,但拥有独立的内存空间。这里我们简单的举例来展示怎么创建进程,以及展示究竟进程是什么。通过运行下面的代码,在这里我使用了fork()
函数来创建一个子进程。fork()
函数会复制当前进程(父进程)并返回新进程的PID。如果fork()
成功执行,它会在父进程中返回子进程的PID,而在子进程中返回0。结果如下:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
// fork 失败
fprintf(stderr, "Fork failed\n");
return 1;
} else if (pid == 0) {
// 子进程执行的代码
printf("This is the child process. PID: %d\n", getpid());
} else {
// 父进程执行的代码
printf("This is the father process. PID: %d, Child PID: %d\n", getpid(), pid);
}
return 0;
}
2.4.进程间通信:
由于进程拥有独立的内存空间,它们之间无法直接共享数据。常见的进程间通信(IPC)机制包括:1.管道 2.消息队列 3.共享内存 4.信号 5.套接字。
接下来通过一个简单的举例,来展示进程间是如何进行通信的。首先,我们引用对应的所需的头文件。接下来通过使用int fd【2】来定义一个整型数组fd去存储管道的文件描述符。然后对于管道(上面提及的通信机制之一)我们使用两个文件描述符,fd[0]用于读取数据,fd[1]用于写入数据。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid == 0) {
// 子进程
close(fd[0]);
char msg[] = "Hello from child";
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]);
} else {
// 父进程
close(fd[1]);
char buffer[100];
read(fd[0], buffer, sizeof(buffer));
printf("father received: %s\n", buffer);
close(fd[0]);
}
return 0;
}
3.线程:
3.1.线程的概念:
进程是一个正在执行的程序实例,它拥有自己独立的地址空间、内存、数据和文件描述符。进程是操作系统进行资源分配和任务管理的基本单位。在Linux中,线程可以使用pthread_create()
函数来创建。线程间的通信和数据共享比进程间更容易,因为线程可以直接读写进程的数据段(堆、全局变量等)。
3.2.线程的优点:
1.创建和删除成本小;2.线程共享进程的地址空间,所以上下文的切换更快;3.线程可以直接访问同一进程的全局变量和堆内存,便于数据共享
3.3.线程的创建和管理:
在 Linux 中,可以使用 POSIX 线程库(pthread)创建和管理线程。以下是一个,直接使用 pthread
库创建线程的示例(这里只是展示基本框架):
void* thread_function(void* arg)
:这是一个线程函数,它将在新线程中执行。这个函数接受一个 void*
类型的参数,但实际上并没有使用它。函数返回 NULL
,表示没有返回值。
pthread_t thread;
:定义一个 pthread_t
类型的变量 thread
,用于存储新创建线程的ID。
int result = pthread_create(&thread, NULL, thread_function, NULL);
:调用pthread_create
函数创建一个新线程。第一个参数是一个指向 pthread_t
类型变量的指针,用于存储新线程的ID。第二个参数是线程属性,这里设置为 NULL
,表示使用默认属性。第三个参数是要在新线程中执行的函数的指针。最后一个参数是传递给线程函数的参数,这里也设置为 NULL(空)
。
#include <stdio.h>
#include <pthread.h>
void* thread_function(void* arg) {
printf("Hello from thread! Thread ID: %ld\n", pthread_self());
return NULL;
}
int main() {
pthread_t thread;
int result = pthread_create(&thread, NULL, thread_function, NULL);
if (result != 0) {
fprintf(stderr, "Error creating thread\n");
return 1;
}
pthread_join(thread, NULL);
printf("Thread has finished execution\n");
return 0;
}
3.4.线程同步:
由于线程共享进程的内存空间,多个线程访问共享资源时可能会发生竞争,导致数据不一致。常用的线程同步机制包括互斥锁(mutex)、条件变量(condition variable)和读写锁(rwlock)。
下面是一个互斥锁的示例(这里的话线程还有很多其他的结束的,锁的方式。如果想了解更多,可以给我评论。这里我们先只掌握最基本的)
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock;
int counter = 0;
void* increment_counter(void* arg) {
pthread_mutex_lock(&lock);
counter++;
printf("Counter: %d\n", counter);
pthread_mutex_unlock(&lock);
return NULL;
}
int main() {
pthread_t threads[10];
pthread_mutex_init(&lock, NULL);
for (int i = 0; i < 10; i++) {
pthread_create(&threads[i], NULL, increment_counter, NULL);
}
for (int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL);
}
pthread_mutex_destroy(&lock);
return 0;
}
4.进程和线程的区别:
特性 | 进程 | 线程 |
---|---|---|
内存空间 | 独立 | 共享 |
创建开销 | 大 | 小 |
上下文切换 | 慢 | 快 |
通信方式 | 需要 IPC 机制 | 直接访问共享内存 |
崩溃影响 | 进程崩溃不影响其他进程 | 线程崩溃可能导致整个进程崩溃 |
5.总结:
进程和线程是 Linux 操作系统中多任务处理的关键概念。进程是资源分配的基本单位,拥有独立的内存空间,而线程是执行单元,共享进程的资源。对于二者多加练习,对于在开发大型的项目时会拥有更多的优势。