目录
前言
线程的引入是为了充分利用现代计算机的多核处理能力,提高程序的效率、响应速度和资源利用率。
一、进程和线程的关系
1、引入线程的原因
并发执行:
- 线程允许一个进程中同时执行多个任务。通过在同一个进程内创建多个线程,可以实现并发处理,从而提高程序的效率和响应速度。例如,一个程序可以在一个线程中处理用户界面响应,在另一个线程中进行后台数据处理。
资源共享:
线程共享进程的资源(如内存空间和文件描述符),这使得线程间的通信和数据共享比进程间更加高效和简单。线程之间可以轻松共享数据和资源,而不需要像进程间那样复杂的通信机制(如管道或消息队列)
减少开销:
- 创建和管理线程比创建和管理进程的开销要小得多。线程的创建和销毁成本较低,因为线程共享同一进程的地址空间和资源,不需要像进程那样进行内存映射和管理。
提高响应性:
- 在多线程程序中,某个线程的任务(如I/O操作)不会阻塞其他线程的执行。这意味着即使一个线程正在等待某些操作完成,其他线程仍然可以继续运行,从而保持程序的响应性。
- 更好的资源利用:
- 在多核处理器上,多线程程序可以将多个线程分配到不同的处理器核心上,从而提高程序的并行处理能力。这种并行执行能够更有效地利用计算资源,提升性能。
2、线程的特点
- 在同一个进程中的多个线程共享进程资源
- 在进程中执行的代码叫主线程,线程执行的代码叫子线程
- 一个进程中至少有一个主线程,可以有0个线程
3、线程和进程的关系
线程和进程是操作系统中两个重要的概念,它们之间有着密切的关系,但又有各自独特的特点。以下是对线程和进程关系的讲述:
进程(Process):
1. **定义**:
- 进程是操作系统中资源分配的基本单位。它是一个正在运行的程序的实例,包括程序代码、数据、堆栈、寄存器状态以及进程控制块等信息。2. **资源管理**:
- 每个进程都有自己的独立地址空间、堆栈、文件描述符等资源。进程间的数据共享和通信需要通过特定的机制,如管道、消息队列、共享内存等。3. **独立性**:
- 进程是相互独立的,一个进程的崩溃通常不会直接影响其他进程。每个进程在内存中都有自己的地址空间。线程(Thread):
1. **定义**:
- 线程是进程中的一个执行单元,是进程内部的最小调度单位。一个进程可以包含一个或多个线程,这些线程共享进程的资源,但各自有自己的执行路径和栈。2. **资源共享**:
- 同一进程中的所有线程共享该进程的地址空间和资源,如内存、文件描述符等。这使得线程间的通信和数据共享更高效,因为它们不需要像进程间那样使用复杂的通信机制。3. **轻量级**:
- 线程的创建和销毁成本较低,相比于进程,它们在切换上下文时的开销也较小,因为线程之间共享相同的进程资源,不需要重新分配内存。### 关系总结:
- **包含关系**:
- 一个进程可以包含多个线程。线程是进程内部的执行单元,而进程是管理和分配资源的单位。- **资源共享**:
- 同一进程中的线程共享进程的资源,如内存和文件描述符。而不同进程之间则拥有独立的资源空间,资源共享和通信更复杂。- **独立性与并发**:
- 进程之间是相互独立的,进程崩溃通常不会直接影响其他进程。线程之间则可以并发执行,共享资源,使得多线程的程序能够提高效率和响应性,但也带来多线程同步和安全的问题。- **开销与效率**:
- 线程的创建和管理开销小于进程,因为线程共享进程资源,无需分配和管理新的地址空间。进程的开销较大,但提供了更好的隔离性和安全性。通过理解线程和进程的关系,可以更好地设计和管理程序,以实现高效的并发和资源利用。
二、如何在进程中创建线程
1、创建线程的函数
1、pthread_create():创建一个线程
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能:在进程中创建一个线程 参数: 参数1: pthread_t *thread:指针地址,指针对应的空间存储创建后的线程id 参数2: const pthread_attr_t *attr:线程的属性结构体指针,指定创建的线程的相关属性 NULL:使用默认属性 参数3: void *(*start_routine) (void *):函数指针,函数地址,存储 返回类型为 void * 参数为void * 函数的地址 该函数指针是 线程的执行函数, 当创建线程后,线程就执行该函数地址对应的函数 参数4: void *arg:当创建线程后,执行线程函数时,线程函数的参数 返回值: 成功,返回0 失败,返回非0
2、pthread_exit():结束当前线程,并把结束状态利用参数地址传递出去
#include <pthread.h> void pthread_exit(void *retval); 功能:退出当前线程,同时把参数作为结束状态返回给回收资源的线程 参数: void *retval:退出当前线程时,传递线程的退出状态值,是一个指针类型(地址) 如果不想传递结束状态,则 填 NULL 当线程退出后,会有部分资源没有回收,需要使用pthread_join函数回收
3、pthread_join():阻塞等待指定线程的结束状态,回收指定线程资源
int pthread_join(pthread_t thread, void **retval); 功能:阻塞等待指定线程退出,接收该线程的退出状态,回收该线程的资源 参数: 参数1: pthread_t thread:指定等待哪个线程结束 参数2: void **retval:将目标线程的退出状态值(一级指针)拷贝到该二级指针(一级指针的地址)对应的内存空间中 填 NULL,代表不接受线程的退出状态 返回值: 成功,返回0 失败,返回非0
2、举例使用:
//创建两个线程,一个线程拷贝文件一半,另一个线程拷贝后半段 #include<stdio.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<pthread.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> //线程1拷贝文件的一半 void * thread_1(void *args) { //打开文件 int pid=open("1.txt",O_RDONLY); //拷贝文件的一半进入2.txt int pid_write1=open("2.txt",O_WRONLY|O_CREAT|O_TRUNC,0664); if(pid_write1<0) { printf("open failed\n"); return NULL; } char buf[50]; int num=*(int*)args; printf("进程1中的大小=%d\n",num); int size=num/2; while(1) { int ret=read(pid,buf,1); size--; if(size==0) { break; } write(pid_write1,buf,ret); } close(pid_write1); char * st=malloc(sizeof(char)*5); st="r"; pthread_exit(st); } //线程2拷贝文件的另一半 void * thread_2(void *argz) { //打开文件 int pid=open("1.txt",O_RDONLY); //拷贝文件的一半进入3.txt int pid_write2=open("3.txt",O_WRONLY|O_CREAT|O_TRUNC,0664); if(pid_write2<0) { printf("open failed\n"); return NULL; } char buf[50]; int num=*(int *)argz; lseek(pid,num/2,SEEK_SET); while(1) { int ret=read(pid,buf,1); num--; if(num==0) { break; } write(pid_write2,buf,ret); } close(pid_write2); //定义结束状态地址,是一个地址, char *p=malloc(sizeof(char)*5); p="h"; pthread_exit(p); } int main(int argc, const char *argv[]) { //打开文件 int pid=open("1.txt",O_RDONLY); //计算文件大小 int size=lseek(pid,0,SEEK_END); printf("%d\n",size); //计算完毕,将文件放到开头 lseek(pid,0,SEEK_SET); close(pid); pthread_t tid1; pthread_t tid2; //创建两个线程 //创建线程1 int temp=pthread_create(&tid1,NULL,thread_1,&size); if(temp==0) { printf("创建成功\n"); } //创建线程2 pthread_create(&tid2,NULL,thread_2,&size); void *st; void *p; //接收指定线程的结束状态 pthread_join(tid1,&st); pthread_join(tid2,&p); return 0; }
三、线程间的同步互斥机制
1、什么是同步互斥机制
这是我第二次讲述同步互斥机制,这个机制主要是帮助我们在使用共享资源时,系统帮助我们完成共享资源调用不会出现错误的一个机制。,不清楚的可以看我上一篇文章
这里面讲述了为什么要使用同步互斥机制,在进程中有同步互斥,那么在线程中同样有这样的机制。
同步:线程间访问共享资源有顺序(唯一性,只有线程访问)
互斥:保证共享资源的完整性,只能同时有一个线程访问
2、如何在线程中使用同步互斥机制
在进程中使用同步互斥机制时,我们采用了信号灯集,信号量的方式来进行,信号的传输,这样就能实现同步和互斥的机制,我们一般使用下面的函数来进行线程间信号灯的使用。这里使用信号灯的函数和进程中使用信号灯的函数不一样,注意区别
1、sem_init():创建信号灯
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); 功能: 创建并初始化信号量值 参数: 参数1: sem_t *sem:信号量变量的地址,把申请到的信号量存储到指定的内存地址空间 参数2: int pshared:共享标识 0:用于线程的同步互斥 非0:用于进程的同步互斥 参数3: unsigned int value:指定信号量的初始值 返回值: 成功,返回0 失败,返回-1,设置错误码
2、sem_wait():申请信号量,申请成功对信号量-1
#include <semaphore.h> int sem_wait(sem_t *sem); 功能:申请信号量,对信号量的值-1 若信号量的值大于0,则申请信号量成功,此时把信号量的值-1,执行操作共享资源的代码 若信号量的值等于0,则申请信号量失败,当前线程进入休眠阻塞,等待信号量的值大于0 参数: sem_t *sem:要进行申请信号量的变量地址 返回值: 成功,返回0 失败,返回-1
3、sem_post():释放信号量,释放成功,对信号量+1
#include <semaphore.h> int sem_post(sem_t *sem); 功能:释放信号量,对信号量的值+1 参数: sem_t *sem:要进行释放信号量的变量地址 返回值: 成功,返回0 失败,返回-1
3、实际举例
//创建三个线程,每个线程循环打印自己的进程号,循环顺序ABC #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<string.h> #include<stdlib.h> #include<sys/types.h> #include<semaphore.h> sem_t A,B,C; //创建线程 void *thread_funcA(void *args) { while(1) { sem_wait(&C); printf("pidA=%d\n",getpid()); sleep(1); sem_post(&A); } return NULL; } void *thread_funcB(void *args) { while(1) { sem_wait(&A); printf("pidB=%d\n",getpid()); sleep(1); sem_post(&B); } return NULL; } void *thread_funcC(void *args) { while(1) { sem_wait(&B); printf("pidC=%d\n",getpid()); sleep(1); sem_post(&C); } return NULL; } int main(int argc, const char *argv[]) { sem_init(&A,0,0); sem_init(&B,0,0); sem_init(&C,0,1); pthread_t tidA,tidB,tidC; //在进程中创建线程 int retA = pthread_create(&tidA,NULL,thread_funcA,NULL); if(retA!=0) { printf("A no error\n"); } int retB = pthread_create(&tidB,NULL,thread_funcB,NULL); if(retB!=0) { printf("B no error\n"); } int retC = pthread_create(&tidC,NULL,thread_funcC,NULL); if(retC!=0) { printf("C no error\n"); } //主线程回收子线程资源,通过地址来找到要回收的信息 void *pthread_pA; void *pthread_pB; void *pthread_pC; pthread_join(tidA,pthread_pA); pthread_join(tidB,pthread_pB); pthread_join(tidC,pthread_pC); return 0; }
总结
进程是资源分配的单位,线程是进程中的执行单元。一个进程可包含多个线程,线程共享进程的资源(如内存),但拥有自己的栈和执行路径。线程开销小、效率高,进程之间独立、资源隔离较好。进程间通信复杂。除此之外,还引入了进程间通信的同步互斥机制,这个机制只要是进程或者是线程在调用共享资源时,大部分情况下都会用到