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

I\O进程线程(Day30)

一、学习内容

  1. 多线程基础

    1> 线程是任务器调度的基本单位,是进程的一个执行单元

    2> 一个进程中可以包含多个线程,但是至少要包含一个线程称为主线程

    3> 一个进程中的多个线程共享进程的资源,不会为线程再 单独分配内存空间

    4> 线程的切换比进程的切换效率会更高,而且所占用内存会更小(8K左右)

    5> 对于多线程编程,需要使用第三方库,pthread库,使用相关函数后,编译程序时,需要加上库的链接: -lpthread

    6> 多线程也可以实现多任务的并发执行,并且比多进程效率会更高,所占内存更小,所以在实现并发操作中,一般常用多线程实现,而不是多进程

  2. 多线程的编程

    • pthread_create(创建线程)

      int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
      • 返回值
        成功返回0, 失败返回一个错误码(该错误码是pthread库中定义的错误码,而不是内核定义的错误码)
      • 参数

        参数1:创建的线程的线程号,使用地址传递进行返回给主线程

        参数2:线程的属性,一般填NULL,表示使用默认的线程属性

        参数3:是线程体函数指针,需要传递一个函数名,该函数返回值类型为void* , 参数为 void *

        参数4:当前线程向新线程传递的数据,也就是参数3的参数

      • 功能
        在当前进程中,启动一个线程
    • pthread_self(线程号获取)

      pthread_t pthread_self(void);
      • 返回值
        当前线程的线程号
      • 参数
      • 功能
        获取当前线程的线程号
    • pthread_exit(退出线程)

      void pthread_exit(void *retval);
      • 返回值
      • 参数
        线程退出时的状态,一般填NULL
      • 功能
        退出当前线程
    • pthread_join(线程资源的回收)

      int pthread_join(pthread_t thread, void **retval);
      • 返回值
        成功返回0,失败返回一个错误码
      • 参数

        参数1:要回收的线程id号

        参数2:接受线程退出时的状态,一般为NULL

      • 功能
        阻塞等待指定线程的退出,并将其资源吸收到自身身上
    • pthread_detach(线程资源回收)

      int pthread_detach(pthread_t thread);
      • 返回值
        成功返回0,失败返回一个错误码
      • 参数
        要被分离的线程号
      • 功能
        非阻塞形式,将线程设置成分离态,被设置成分离态的线程退出后,其资源由系统进行回收
    • 多线程之间的数据通信

      对于同一个进程的多个线程而言,他们是共用一个进程的资源。虽然不能使用局部变量进行相互通信,但是,可以使用全局变量来进行资源的通信
      • 临界区
        每个线程中,访问临界资源的代码段
      • 临界资源
        多个线程共享全局资源、堆区资源
  3. 竞态

    当多个线程共同使用同一个临界资源,可能会出现当一个线程正在处理数据时,其时间片可能会结束,另一个线程启动后,可将临界资源进行更改,等到再执行该线程时,数据就发生错误了。这种多个线程抢占同一个进程的临界资源的现象
    • 解决方式

      • 互斥机制
        使用互斥锁来完成,互斥锁也是一个特殊的临界资源,当某个线程获得了互斥锁后,其余线程只能等待该线程释放互斥锁后,再进行抢占。同一时刻,只能一个线程拥有互斥锁
      • 同步机制
        使用同步机制,可以完成多个线程有顺序的访问临界资源,这样也起到保护临界资源的作用
  4. 多进程和多线程相关函数的对比

  5. 线程的同步互斥机制

    • 互斥机制
      互斥机制使用的是互斥锁,是一种特殊的临界资源:不能同时被两个不同的线程锁定,如果一个线程试图去锁定一个已经被其他线程锁定的互斥锁时,那个线程将被挂起,直到该线程释放了该互斥锁
      • 销毁锁资源
        int pthread_mutex_destroy(pthread_mutex_t *mutex);
        • 返回值
          成功返回0,失败返回错误码
        • 参数
          互斥锁地址
        • 功能
          销毁互斥锁
      • 释放锁资源
        int pthread_mutex_unlock(pthread_mutex_t *mutex);
        • 返回值
          成功返回0,失败返回错误码
        • 参数
          互斥锁地址
        • 功能
          释放锁资源
      • 获取锁资源
        int pthread_mutex_lock(pthread_mutex_t *mutex);
        • 返回值
          成功返回0,失败返回错误码
        • 参数
          互斥锁地址
        • 功能
          获取锁资源,如果该互斥锁已经被锁定,那么当前线程会在该函数处阻塞
      • 初始化互斥锁
        int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
        • 返回值
          总是返回0
        • 参数

          参数1:互斥锁的地址,用于更改互斥锁内容

          参数2:互斥锁属性,一般填NULL,表示使用默认属性

        • 功能
          初始化一个互斥锁
      • 创建互斥锁
        pthread_mutex_t mutex;
    • 同步机制之无名信号量

      • 概念
        无名信号量中,本质上维护了一个value值,任何一个线程都可以申请该值和释放该值。当某个线程申请该值时,该值就会减少,该操作称为 P 操作。当某个线程释放该资源时,该值就会增加,该操作称为 V 操作。当value的值为0时,申请资源的线程会在申请处阻塞,直到某个线程将该资源增加到大于0
        •  同步机制主要实现的是:生产者消费者模型

        • 1同步:表示的是多个进程有先后顺序的执行

      • 创建一个无名信号量
        sem_t sem;
      • 初始化无名信号量
        int sem_init(sem_t *sem, int pshared, unsigned int value);
        • 返回值
          成功返回0,失败返回-1,并置位错误码
        • 参数

          参数1:要被初始化的无名信号量的地址

          参数2:判断是多进程还是多线程的同步

          0:表示多线程之间的同步
          非0:表示多进程之间的同步(亲缘进程间同步)

          参数3:value的初始值

        • 功能
          初始化一个无名信号量
      • 申请资源P操作
        int sem_wait(sem_t *sem);
        • 返回值
          成功返回0,失败返回-1并置位错误码
        • 参数
          无名信号量的地址
        • 功能
          申请无名信号量的资源,使得无名信号量维护的value值减1操作,如果该无名信号量中的值为0,则当前线程在该函数处阻塞
      • 释放资源 V操作
        int sem_post(sem_t *sem);
        • 返回值
          成功返回0,失败返回-1并置位错误码
        • 参数
          无名信号量地址
        • 功能
          是否无名信号量中的资源,是的无名信号量维护的value值加1操作
      • 销毁无名信号量
        int sem_destroy(sem_t *sem);
        • 返回值
          成功返回0,失败返回-1并置位错误码
        • 参数
          无名信号量的地址,该无名信号量必须要是使用sem_init初始化的无名信号量
        • 功能
          销毁一个无名信号量
  6. 脑图

二、作业

作业1:

使用多线程完成两个文件的拷贝,分支线程1,拷贝前一半,分支线程2拷贝后一半,主线程用于回收分支线程的资源

代码解答:

#include <myhead.h>// 声明全局变量
pthread_mutex_t mutex;  // 互斥锁
int source = -1;        // 源文件描述符
int dest = -1;          // 目标文件描述符
int len = 0;            // 源文件总长度
int half_len = 0;       // 源文件的一半长度// 线程1:负责拷贝源文件的前半部分
void *task1(void *arg)
{char rbuf[128] = "";          // 用于存储读取的数据的缓冲区lseek(source, 0, SEEK_SET);   // 设置源文件指针到文件开头int sum = 0;                  // 记录读取的字节总数while (sum < half_len)        // 循环读取直到读取的字节数达到文件的一半{pthread_mutex_lock(&mutex);  // 加锁,防止其他线程干扰文件操作int res = read(source, rbuf, sizeof(rbuf));  // 读取数据到缓冲区if (res == 0)               // 如果读取到文件末尾,退出循环{pthread_mutex_unlock(&mutex);  // 解锁break;}sum += res;  // 更新已读取的字节总数// 如果超过文件的一半,仅写入多余部分,避免越界写入if (sum > half_len){write(dest, rbuf, res - (sum - half_len));  // 写入目标文件的剩余部分}else{write(dest, rbuf, res);  // 写入读取的完整数据}pthread_mutex_unlock(&mutex);  // 解锁,允许其他线程操作}pthread_exit(NULL);  // 线程结束
}// 线程2:负责拷贝源文件的后半部分
void *task2(void *arg)
{char rbuf[128] = "";           // 用于存储读取的数据的缓冲区lseek(source, half_len, SEEK_SET);  // 设置源文件指针到文件的中间位置int res;while (1){pthread_mutex_lock(&mutex);  // 加锁,防止其他线程干扰文件操作res = read(source, rbuf, sizeof(rbuf));  // 读取数据到缓冲区if (res == 0)               // 如果读取到文件末尾,退出循环{pthread_mutex_unlock(&mutex);  // 解锁break;}write(dest, rbuf, res);  // 将读取的数据写入目标文件pthread_mutex_unlock(&mutex);  // 解锁,允许其他线程操作}pthread_exit(NULL);  // 线程结束
}int main(int argc, const char *argv[])
{pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁// 打开源文件,只读模式if ((source = open("./source.txt", O_RDONLY)) == -1){perror("open source error");  // 打开失败,输出错误信息return -1;}// 打开目标文件,写模式,如果不存在则创建,存在则清空内容if ((dest = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1){perror("open dest error");  // 打开失败,输出错误信息close(source);  // 关闭已打开的源文件return -1;}// 获取源文件的长度len = lseek(source, 0, SEEK_END);if (len == -1){perror("lseek error");  // 获取文件长度失败,输出错误信息close(source);  // 关闭源文件close(dest);    // 关闭目标文件return -1;}// 计算源文件的一半长度half_len = (len + 1) / 2;// 创建线程1,负责拷贝前半部分pthread_t tid1, tid2;if (pthread_create(&tid1, NULL, task1, NULL) != 0){printf("tid1 create error\n");  // 线程1创建失败return -1;}// 创建线程2,负责拷贝后半部分if (pthread_create(&tid2, NULL, task2, NULL) != 0){printf("tid2 create error");  // 线程2创建失败return -1;}// 打印线程的ID,便于调试printf("tid1=%#lx,  tid2=%#lx\n", tid1, tid2);// 等待线程1、2执行结束pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&mutex);  // 销毁互斥锁close(source);  // 关闭源文件close(dest);    // 关闭目标文件printf("拷贝成功\n");  // 拷贝成功提示return 0;
}

成果展现: 

作业2:

互斥锁基础的使用代码

代码解答:

#include <myhead.h>int money=10000; 		//全局变量  临界资源//创建一个互斥锁
pthread_mutex_t mutex;//创建取钱任务1
void *task1(void *arg)
{while(1){ 	sleep(1);//获取锁资源pthread_mutex_lock(&mutex);if(money<500){//释放锁资源pthread_mutex_unlock(&mutex);break;}money-=500;printf("张三消费500元,剩余%d元\n",money);pthread_mutex_unlock(&mutex);}pthread_exit(NULL);
}void *task2(void *arg)
{while(1){sleep(1);//获取锁资源pthread_mutex_lock(&mutex);if(money<200){	pthread_mutex_unlock(&mutex);break;}money-=200;printf("李四消费200元,剩余%d元\n",money);//释放锁资源pthread_mutex_unlock(&mutex);}pthread_exit(NULL);
}/**************主程序****************/
int main(int argc, const char *argv[])
{//初始化互斥锁pthread_mutex_init(&mutex,NULL);//启动多个线程用于消费pthread_t tid1,tid2;if(pthread_create(&tid1,NULL,task1,NULL)!=0){printf("tid1 create error\n");return -1;}if(pthread_create(&tid2,NULL,task2,NULL)!=0){printf("tid2 create error\n");return -1;}printf("tid1=%#lx,tid2=%#lx\n",tid1,tid2);pthread_join(tid1,NULL);pthread_join(tid2,NULL);//销毁锁资源pthread_mutex_destroy(&mutex);return 0;
}

成果展现: 

作业3:

无名信号量基础的使用代码

代码解答:

#include <myhead.h>
//创建一个无名信号量
sem_t sem;//定义生产者线程
void *task1(void *arg)
{int num=5;while(num--){sleep(1);printf("%#lx:我生产了一辆小米SU7\n",pthread_self());//释放无名信号量sem_post(&sem);}//退出线程pthread_exit(NULL);
}void *task2(void *arg)
{int num =5;while(num--){//申请资源sem_wait(&sem);printf("%#lx:我消费了一辆小米SU7\n",pthread_self());}pthread_exit(NULL);
}int main(int argc, const char *argv[])
{//初始化无名信号量sem_init(&sem,0,0);//第一个0:表示用于多线程的同步//第二个0;表示初始化value值为0//创建两个任务线程pthread_t tid1,tid2;if(pthread_create(&tid1,NULL,task1,NULL) !=0){printf("tid1 create error\n");return -1;}if(pthread_create(&tid2,NULL,task2,NULL)!=0){printf("tid2 create error\n");return -1;}//主线程序中printf("tid1=%#lx,tid2=%#lx\n",tid1,tid2);//阻塞等分支线程的结束pthread_join(tid1,NULL);pthread_join(tid2,NULL);sem_destroy(&sem);return 0;
}

成果展现: 

三、总结

学习内容概述:

1. 线程创建与控制

使用 `pthread_create` 创建线程,`pthread_self` 获取线程号,`pthread_exit` 退出线程,`pthread_join` 回收线程资源,`pthread_detach` 实现非阻塞线程分离。

2. 线程间通信

线程共享全局资源,通过全局变量通信,临界区和临界资源的概念。

3. 互斥机制

通过互斥锁(`pthread_mutex_lock` 和 `pthread_mutex_unlock`)保护临界资源,防止竞态条件的发生。

4. 同步机制

使用无名信号量实现线程之间的同步,确保多个线程有序访问临界资源。

 学习难点:

1. 竞态条件和互斥机制

当多个线程同时访问共享资源时,容易引发竞态条件,理解如何通过互斥锁机制保护资源是一大难点。

2. 线程的同步

同步机制的实现(如无名信号量)涉及线程的协调和资源的有序访问,这需要深刻理解信号量的操作模式(P操作和V操作)以及如何避免死锁。

3. 线程回收与分离

线程资源的回收机制涉及到阻塞与非阻塞的区别,如何根据需求选择 `pthread_join` 和 `pthread_detach` 是一个细节上的难点。

主要事项:

1. 互斥锁的使用

在多线程访问临界资源时,务必通过互斥锁保护资源,避免多个线程在同一时间修改共享资源,确保数据的一致性。

2. 信号量同步

合理使用无名信号量来控制线程的同步,确保在某些线程完成资源操作之前,其他线程不会抢先访问资源。

3. 线程的生命周期管理

理解线程的创建、执行、退出和资源回收是多线程编程的重要基础,避免僵尸线程或资源泄漏。

未来学习的重点:

1. 多线程中的高级同步机制

如条件变量和读写锁,这些同步机制在复杂场景中更有效地管理资源访问。

2. 线程池的实现

提升多线程程序的效率和资源管理,减少频繁创建和销毁线程的开销。

3. 多线程的调试技巧

学习如何使用调试工具分析多线程程序中的竞态条件、死锁和线程同步问题。


http://www.mrgr.cn/news/53327.html

相关文章:

  • 宽表和窄表
  • vb操作电子表格 增加工作表 工作表数据合并
  • 【Linux】Anaconda下载安装配置Pytorch安装配置(保姆级)
  • [Python基础](2) 基本数据类型
  • 车辆数据的提取、定位和融合(其三.一 共十二篇)子映射和时间加权
  • vb6 MSHFlexGrid1表格导出数据到电子表格 解决只能导出一次问题
  • VSCode C/C++跳转到定义、自动补全、悬停提示突然失效
  • 字节青训营入营考核部分题解
  • 海康相机
  • 【前端】如何制作一个自己的网页(9)
  • 恋爱脑讲编程:Rust 的生命周期概念
  • 征服ES(ElasticSearch)的慢查询实战
  • 网络空间安全之一个WH的超前沿全栈技术深入学习之路(一:渗透测试行业术语扫盲)作者——LJS
  • 【C++】unordered_set、unordered_map超详细封装过程,处理底层细节
  • 【前端】Matter实战:HTML游戏”命悬一线“
  • 5、JavaScript(五) jquery
  • 牛客习题—线性DP 【mari和shiny】C++
  • 【前端】如何制作一个自己的代码(10)
  • Linux之例行性工作
  • 吴恩达深度学习笔记(8)