进程与线程编程并实现累加和计算
进程
- 进程是程序执行时的一个实例。它包括程序的代码、数据和进程控制块等资源。
- 每个进程都有自己的地址空间,相互之间独立,一个进程的数据通常无法直接被其他进程访问。
- 进程之间的通信需要通过显式的IPC(Inter-Process Communication,进程间通信)机制,例如管道、信号、消息队列等。
- 进程是系统分配资源的基本单位,每个进程都有自己的内存空间、文件描述符、环境变量等。
- 进程的创建和销毁比较耗费系统资源,因为需要分配和释放大量资源。
线程
- 线程是进程内的一个执行单元。一个进程可以包含多个线程,它们共享相同的地址空间和其他资源。
- 多线程可以提高程序的并发性,因为多个线程可以在同一时间内执行不同的任务,从而提高程序的效率。
- 线程之间的通信更加方便,因为它们共享相同的内存空间,可以直接读写共享变量。
- 线程的创建和销毁比进程轻量级,因为它们共享相同的资源,创建和销毁的开销较小。
fork()函数介绍
所需头文件
#include<unistd.h>
#include<sys/types.h>
函数定义 : pid_t fork( void );
pid_t 是一个宏定义,其实质是int 被定义在#include
中
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
函数说明:
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。父子进程间共享的存储空间只有代码段。
fork()
创建的新进程拥有父进程数据段、堆和栈的副本,但是这两个进程的这些部分在物理内存中是独立的。父子进程只共享代码段。
代码示例:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
pid_t pid;/*pid 进程id号*/
pid=fork();/*创建一个新进程*/
if(pid==0) /*返回0为子进程*/
{
printf("Return pid is %d\n",pid);
printf("This is son process! pid is:%d\n",getpid());
}
else if(pid>0)/*返回大于0为父进程*/
{
printf("Return pid is %d\n",pid);
printf("This is parent process! pid is:%d\n",getpid());
waitpid(pid,NULL,0);/*等待子进程退出*/
}
else
{
perror("fork() error!");
exit;
}
}
pthread_create()函数的使用
pthread_create()
是 POSIX 线程库中用于创建新线程的函数。POSIX 线程,或 “pthread”,是一个可移植的线程标准,定义了线程的创建、控制和终止等操作。pthread_create()
调用会创建一个新的线程并将其加入到当前进程中。新线程从指定的函数地址开始执行。
#include<pthread.h>
int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict_attr,
void*(*start_rtn)(void*),void *restrict arg);
参数:
- 第一个参数为指向线程标识符的指针。
- 第二个参数用来设置线程属性。一般为NULL,表示默认属性
- 第三个参数是线程运行函数的起始地址。
- 最后一个参数是运行函数的参数。
另外,在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库
返回:
若成功则返回0,否则返回出错编号
返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。 编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。
代码示例:
#include <iostream>
#include <pthread.h>
using namespace std;
pthread_t thread;
void fn(void *arg)
{
int i = *(int *)arg;
cout<<"i = "<<i<<endl;
return ((void *)0);
}
int main()
{
int err1;
int i=10;
err1 = pthread_create(&thread, NULL, fn, &i);
pthread_join(thread, NULL);
}
多进程实现累加计算数列和
fork()创建子进程,每个子进程计算数列的一部分,父进程收集计算结果
父子进程之间的通信:管道(pipe)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/time.h>
#define NUM_PROCESSES 10 // 定义子进程数量
// 累加函数,每个子进程执行的代码
long sumRange(int start, int end) {
long sum = 0;
for (int i = start; i <= end; ++i) {
sum += i;
}
return sum;
}
int main() {
int numbersPerProcess = 1000000000 / NUM_PROCESSES;
int pipefds[2 * NUM_PROCESSES]; // 为每个子进程创建一个管道
pid_t pids[NUM_PROCESSES];
struct timeval start, end;
// 获取开始时间
gettimeofday(&start, NULL);
// 创建管道
for (int i = 0; i < NUM_PROCESSES; ++i) {
if (pipe(pipefds + i*2) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
}
// 创建子进程
for (int i = 0; i < NUM_PROCESSES; ++i) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pids[i] == 0) { // 子进程
close(pipefds[i*2]); // 关闭读端
int start = i * numbersPerProcess + 1;
int end = (i + 1) * numbersPerProcess;
long partialSum = sumRange(start, end);
write(pipefds[i*2 + 1], &partialSum, sizeof(partialSum)); // 写入计算结果
close(pipefds[i*2 + 1]); // 关闭写端
exit(EXIT_SUCCESS);
}
}
// 父进程
long totalSum = 0, readSum = 0;
// 等待子进程并读取其计算结果
for (int i = 0; i < NUM_PROCESSES; ++i) {
close(pipefds[i*2 + 1]); // 关闭写端
read(pipefds[i*2], &readSum, sizeof(readSum));
totalSum += readSum;
close(pipefds[i*2]); // 关闭读端
wait(NULL); // 等待子进程结束
}
// 获取结束时间
gettimeofday(&end, NULL);
// 计算并打印执行时间
long seconds = end.tv_sec - start.tv_sec;
long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
printf("NUM_PROCESSES = %d\n", NUM_PROCESSES);
printf("Total Sum = %ld\n", totalSum);
printf("Time taken: %ld microseconds (%.3f seconds)\n", micros, micros / 1000000.0);
return 0;
}
多线程实现累加和计算
使用多线程实现累加和计算,我们可以将要累加的数值范围分割给多个线程,每个线程计算自己那部分的和,最后将所有线程的结果累加起来得到最终结果。这里,我们将使用 POSIX 线程库(pthread)来实现这个多线程累加和计算。
以下是一个简单的实现方案:
- 定义一个结构体来传递给每个线程,这个结构体包含了线程需要知道的信息,比如计算的起始和结束值。
- 创建多个线程,每个线程负责计算一部分数值的和。
- 等待所有线程完成,然后汇总每个线程计算的结果。
- 输出最终的累加和。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#define NUM_THREADS 10 // 定义线程数量
typedef struct {
int start;
int end;
long sum; // 用于存储这个线程计算的部分和
} ThreadData;
// 线程函数
void* sumRange(void* arg) {
ThreadData* data = (ThreadData*)arg;
data->sum = 0;
for (int i = data->start; i <= data->end; ++i) {
data->sum += i;
}
pthread_exit((void*) &(data->sum));
}
int main() {
pthread_t threads[NUM_THREADS];
ThreadData threadData[NUM_THREADS];
int numbersPerThread = 1000000000 / NUM_THREADS;
long totalSum = 0;
struct timeval start, end;
// 获取开始时间
gettimeofday(&start, NULL);
// 创建线程
for (int i = 0; i < NUM_THREADS; ++i) {
threadData[i].start = i * numbersPerThread + 1;
threadData[i].end = (i + 1) * numbersPerThread;
pthread_create(&threads[i], NULL, sumRange, (void*)&threadData[i]);
}
// 等待线程完成并汇总结果
for (int i = 0; i < NUM_THREADS; ++i) {
void* status;
pthread_join(threads[i], &status);
totalSum += *(long*)status;
}
// 获取结束时间
gettimeofday(&end, NULL);
// 计算并打印执行时间
long seconds = end.tv_sec - start.tv_sec;
long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
printf("NUM_THREADS = %d\n", NUM_THREADS);
printf("Total Sum = %ld\n", totalSum);
printf("Time taken: %ld microseconds (%.3f seconds)\n", micros, micros / 1000000.0);
return 0;
}