多线程详解

目录

1.线程的概念

2.进程和线程的区别

3.线程操作

4.线程互斥

5.可重入和线程安全

6.常见锁的概念

7.线程同步


1.线程的概念(lwp)

1.1 线程概念

(1) 线程是cpu调度的基本单位(轻量化的进程), 和进程不同的是进程是承担系统资源的基本实体.

(2) 一个进程里面至少有一个线程.

(3) 线程是在进程的地址空间里面运行的.

 1.2 线程的优点(和进程比较)

(1) 创建线程的代价要小;

(2) 线程的切换对于操作系统来说工作量更少;(因为寄存器少,不需要重新更新cache)

(3) 线程占用得资源要小;

(4) 能充分利用多处理器的并行数量;

(5) 等待i/o操作结束的时候,还可以执行其他任务;

1.3线程的缺点:

(1) 性能降低; 

(2) 健壮性降低;

(3) 缺乏访问限定;

(4) 代码编写难度大;

 1.3 地址空间

文件i/o的基本单位大小是4KB;  因为一共是32位数据,那么一共可以存下2^32byte数据, 一个页框是4kB大小就是2^12个byte; 那么算下来就是可以分1048576个页框. 那么页表如何能够存下页框这么多的信息的呢? 首先将前10位的数据存放到页目录里面, 根据这10位数据以及和接着的10位数据找到在页表的位置找到页框的地址, 最后12位数据用来和页框初始地址找到最后的具体位置.

注意: 划分页表的本质就是在划分地址空间.

1.4 线程异常

(1) 如果单个线程发生了异常, 那么其他线程以及进程也必定会受到崩溃.因为线程是进程执行的分支, 如果线程出错, 导致进程直接终止, 那么进程的其他执行流的线程也会终止.

2.进程和线程的区别

2.1区别

(1) 进程是系统资源分配的基本单位; 线程是cpu调度的基本单位;

(2) 线程共享进程的数据, 但是也拥有自己的一部分数据;

线程id, errno, 栈, 一组寄存器, 信号屏蔽字, 调度优先级.

(3) 进程的多个线程共享同一个地址空间, 如果定义一个函数, 在每个线程内都可以使用, 全局变量也可以. 线程还共享进程的环境和资源.

3.线程操作

3.1线程的创建

头文件: pthread.h; 连接线程函数库的时候要使用到: -lpthread; 

首先第一个参数thread就是线程的id;

attr是线程的属性(一般为nullptr);

start_routine是线程需要执行的函数;

arg是传递给线程执行函数的参数.

注意:

(1) 创建失败不会返回将全局变量errno返回, 是将错误代码通过返回值返回.

线程也提供了errno变量, 但是通过返回值更加好判断错误信息.

(2) pthread_create创建的线程id和线程id不是一回事; 前面是由第一个参数指向虚拟内存本质就是内存单元的地址,  后续线程的操作都是根据这个id来使用的, 而后面的是采用了一个数值来唯一表示了id.

 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>
#include<string>
using namespace std;int gcnt = 100;void* ThreadRountine(void *args)
{string threadname = (const char*)args;while(true){cout << "new thread" << threadname << "pid" << getpid() << "gcnt" << gcnt << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRountine, (void*)"thread 1");//传参//pthread_t tid1;//pthread_create(&tid, nullptr, ThreadRountine, (void*)"thread 1");sleep(3);//主线程;while(true){cout << "main thread" << getpid() << "gcnt:" << gcnt << endl;sleep(1);}
}

3.2 获取线程自身id

直接放到线程里面使用即可.

3.3 线程终止

(1) pthread_exit

参数value_ptr表示线程退出的状态; 一般都是给nullptr.

 (2) pthread_cancel

作用: 取消进行的线程;

小tips: 为啥要线程等待?

(1) 已经退出的线程, 空间还没有释放还在进程地址空间中;

(2) 新的线程不会复用刚才退出的线程的地址空间;

(3) 防止出现僵尸进程.

(3) pthread_ join

作用:等待线程的终止; 挂起等待, 直到线程终止.

第一个参数表示线程id, 后面是错误码的返回值;

总结:

(1) 如果线程是通过return返回的, 那么retval指向线程函数的返回值;

(2) 如果线程是通过被别的线程异常pthread_cancel返回的, 那么retval是指向常量PTHREAD_CANCEL;

(3) 如果线程是通过pthread_exit返回的, 那么就是指向传给pthread_exit的参数;

(4) 线程是被分离是可以被取消的, 但是不能被join.

 3.4 线程分离

对目标线程进行分离; 线程的分离和join只能存在一个, 不能两者都同时出现.

线程自己也可以自己分离自己.

pthread_detach(pthread_self());

小tips:

        以上的接口都不是系统直接提供的接口, 是原生线程库pthread提供的接口.

如何理解pthread库管理线程?

        线程库是共享的, 并且内部要管理整个系统, 多个用户启动所有进程.

pthread_r tid就是线程属性集合的地址!!!

4.线程互斥

4.1 多线程抢票

先用这个实例情况将多线程出现的问题提出, 然后就可以知道互斥!

看看下面的代码, 结果会是什么?

可能会出现0, -1, -2, 的情况. 这个又是为啥?

因为线程并行地访问了同一个临界资源, 并且--操作还不是原子的, 那么势必会造成由于每个线程的时间片不同对临界资源的修改的结果无人可知, 所以就会出现线程同时大量进入临界资源, 最后修改的数据和理想结果不同.

综上: 我们就会要求每次访问临界资源的只能是一个线程, 那么就是要引入互斥啦!

int ticket = 1000;void* getticket(void* args)
{while(true){if(ticket > 0){cout << "get a ticket!" << ticket << endl;ticket--;}else{break;}}return nullptr;
}int main()
{pthread_t tid1;pthread_create(&tid1, nullptr, getticket, (void*)"thread-1");pthread_t tid2;pthread_create(&tid2, nullptr, getticket, (void*)"thread-2");pthread_t tid3;pthread_create(&tid3, nullptr, getticket, (void*)"thread-3");cout << "main thread" << endl;return 0;
}

4.2线程互斥锁相关接口

动态分配:

(1)锁的创建: pthread_mutex_t mutex

(2)锁的初始化:  pthread_mutex_init

(3)锁的销毁: pthread_destroy

(4) 加锁: pthread_mutex_lock

(5) 解锁: pthread_mutex_unlock

返回值:成功返回0,失败返回错误号

注意:

     pthread_mutex_lock: 如果互斥量没有上锁就返回成功, 如果多个线程抢夺锁资源,如果没有抢夺到就会自动阻塞挂起, 等待解锁.

静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
不需要销毁!!!

 下面使用封装好的线程, 进行互斥(加锁解锁)操作等来验证线程互斥.

//简单封装一个线程创建一系列的操作
#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T)>;template<class T>
class Thread
{
public:Thread(const std::string &threadname, func_t<T> func, T data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}static void *ThreadRoutine(void *args) // 类内方法,{// (void)args; // 仅仅是为了防止编译器有告警Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);if(n == 0) {_isrunning = true;return true;}else return false;}bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}return false;}std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning;}~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};
#include <iostream>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}int ticket = 10000;
void GetTicket(pthread_mutex_t *mutex)
{while (true){pthread_mutex_lock(mutex); if (ticket > 0) {usleep(1000);printf("get a ticket: %d\n", ticket);ticket--;pthread_mutex_unlock(mutex);}else{pthread_mutex_unlock(mutex);break;}}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);std::string name1 = GetThreadName();Thread<pthread_mutex_t *> t1(name1, GetTicket, &mutex);std::string name2 = GetThreadName();Thread<pthread_mutex_t *> t2(name2, GetTicket, &mutex);std::string name3 = GetThreadName();Thread<pthread_mutex_t *> t3(name3, GetTicket, &mutex);std::string name4 = GetThreadName();Thread<pthread_mutex_t *> t4(name4, GetTicket, &mutex);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();pthread_mutex_destroy(&mutex);return 0;
}

4.3 互斥量mutex

(1) 线程使用的绝大部分的变量是局部的, 它们是存放再栈空间, 这种变量是归属与单个线程, 其他线程不拥有.

(2) 也有些变量是共享变量, 全部线程都可以共享使用.

(3) 锁就是互斥量, 锁的作用: 1.当代码进入临界区, 其他线程无法进入该临界区;

      2.多个线程要进入临界区, 现如今没有线程进入时候, 只能允许一个线程进入该区域;

      3.如果线程不在临界区, 也不能阻止其他线程进入临界区.

小tips: swap和exchange是将寄存器和内存单元的数据进行交换, 并且这个指令是原子的.

 

5.可重入和线程安全

可重入: 同一个函数被不同的执行流调用, 当前执行流还没执行完, 其他执行流又来进入, 但是最后执行的结果没有任何不同以及问题;

线程安全:  线程执行同一块代码, 不会出现不一样的结果.

注意: 可重入函数就是线程安全的一种.

 5.1常见线程不安全的情况

(1) 不保护共享变量的函数;

(2) 函数随着被调用发生变化;

(3) 调用线程不安全的函数;

(4) 返回指向静态变量指针的函数;

6.常见锁的概念

6.1死锁

一组线程中都占有不会释放的资源, 当线程和另外一个线程互相申请资源的时候都占用不会释放的资源而永久等待的状态.

 6.2死锁的四个条件

(1) 互斥条件: 一个资源只被一个执行流使用;

(2) 请求与保持条件: 一个执行流对资源进行申请并且对已有的资源不释放.

(3) 不剥夺条件: 一个已经获得资源的执行流在没有执行完前都不会剥夺;

(4) 循环等待条件: 多个执行流之间形成循环等待资源的关系.

6.3避免死锁的方法

(1) 破坏死锁四个条件;

(2) 加锁顺序一致;

(3) 避免锁未释放的情况;

(4) 资源一次性分配;

7 线程同步

7.1同步

在数据安全的前提, 让线程进行特定的顺序访问临界资源, 避免饥饿问题(线程始终分配不到资源).

互斥可以保证资源利用的安全性, 同步可以保证充分高效的使用资源.

 7.2条件变量

条件变量函数:

(1)条件变量的初始化:

int pthread_cond_init: cond参数是需要初始化的条件变量; 

(2)条件变量的销毁: 

int pthread_cond_destroy

(3)等待条件满足:

int pthread_cond_wait: cond是条件变量, mutex是互斥量.

(4) 唤醒等待: 

全部唤醒:  int pthread_cond_broadcast:

单个唤醒:  int pthread_cond_signal: 

 7.3线程同步实例

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int ticket = 1000;void* threadRountine(void* args)
{string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);if(ticket > 0){cout << name << ", get a ticket:" << ticket-- << endl;usleep(1000);}else{cout << "没票了," << name << endl;pthread_cond_wait(&cond, &mutex);}pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t t1, t2, t3;pthread_create(&t1, nullptr, threadRountine,(void*)"thread-1");pthread_create(&t2, nullptr, threadRountine,(void*)"thread-2");pthread_create(&t3, nullptr, threadRountine,(void*)"thread-3");sleep(5);while(true){/* pthread_cond_signal(&cond); *//*   pthread_cond_broadcast(&cond); */sleep(6);pthread_mutex_lock(&mutex);ticket += 100;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);return 0;
}

注意:

(1)线程在等待的时候会自动释放锁资源,;

(2)当线程在临界区唤醒进行pthread_cond_wait, 要重新申请并且持有锁;

(3)线程唤醒时候重新申请并持有锁就是参与竞争锁资源.

小tips:

为什么pthread_cond_wait需要互斥量?

因为条件等待是线程同步的手段, 必须要改变原来的共享变量, 然后原来的线程也可以满足条件得到资源. 条件变量的改变必然会牵扯到共享的数据, 那么必定也要使用到互斥量进行保护资源.

 后言:

线程部分还涉及到生产消费者模型以及基于环形队列的生产消费者模型,以及线程池,线程安全的单例模型.读者写者问题.这些就放到下一博客具体来讲. 喜欢的大家可以三连一下!!!谢谢大家.

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

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

相关文章

10.k8s的附加组件(coreDNS组件)

目录 一、概念 二、查看k8s集群的coreDNS的IP地址 三、验证 一、概念 service发现是k8s中的一个重要机制&#xff0c;其基本功能为&#xff1a;在集群内通过服务名对服务进行访问&#xff0c;即需要完成从服务名到ClusterIP的解析。k8s主要有两种service发现机制&#xff1a;…

mars3d的config,json文件配置谷歌影像地图的tilingScheme属性

mars3d的config,json文件配置tilingScheme属性说明&#xff1a; 1.cesium加载谷歌影像地图的时候需要配置tilingScheme参数&#xff0c;如以下代码&#xff1a; var viewer new Cesium.Viewer("cesiumContainer", { animation: false, //是否显示动画控件 baseLaye…

基于 Ubuntu22.04 安装 SSH 服务

文章目录 一、Ubuntu22.04 安装 SSH 服务二、配置 OpenSSH&#xff08;安全性&#xff09;1. 更改 OpenSSH 端口2. 限制使用 SSH 登录尝试次数3. 禁止 SSH 以 root 身份连接 三、设置防火墙&#xff08;UFW&#xff09;锁定 SSH四、远程终端软件通过 SSH 连接 Ubuntu22.041. 远…

【计算机科学速成课】笔记四

文章目录 19.内存&存储介质课程引出——内存与存储器的区别纸带存储磁芯存储磁带、磁鼓存储磁盘&#xff08;硬盘&#xff09;存储软盘存储光盘存储&#xff08;CD&DVD&#xff09;固态硬盘存储 20.文件系统课程引出——文件格式.txt文本文件.wav 音频文件.bmp位图文件…

【微信小程序开发】微信小程序注册,配置开发者工具

准备工作 微信小程序小程序开发流程 开发过程注册小程序开发者工具开发界面介绍 微信小程序 一种新的开发能力&#xff0c;可以在微信内被便捷的获取和传播&#xff0c;具有出色的用户体验 地址&#xff1a;https://mp.weixin.qq.com/ 注册微信小程序 在进行开发之前我们应该…

原生轮播图(下一页切换,附带指示器)

下面是目录结构&#xff1a; index.html <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" c…

STM32单片机实战开发笔记-独立看门狗IWDG

嵌入式单片机开发实战例程合集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11av8rV45dtHO0EHf8e_Q0Q?pwd28ab 提取码&#xff1a;28ab IWDG模块测试 1、功能描述 STM32F10X内置两个看门狗&#xff0c;提供了更高的安全性&#xff0c;时间的精确下性和使用的灵活性…

58行代码把Llama 3扩展到100万上下文,任何微调版都适用 | 最新快讯

量子位公众号 QbitAI 堂堂开源之王 Llama 3&#xff0c;原版上下文窗口居然只有……8k&#xff0c;让到嘴边的一句“真香”又咽回去了。 在 32k 起步&#xff0c;100k 寻常的今天&#xff0c;这是故意要给开源社区留做贡献的空间吗&#xff1f; 开源社区当然不会放过这个机会&a…

性能工具使用

systemTap 采集工具 bcc 分析工具 sysstat 监控工具 netstat 网络监控工具 分类 tools/argdist: Display function parameter values as a histogram or frequency count. Examples.tools/bashreadline: Print entered bash commands system wide. Examples.tools/bpfli…

Socket学习记录

本次学习Socket的编程开发&#xff0c;该技术在一些通讯软件&#xff0c;比如说微信&#xff0c;QQ等有广泛应用。 网络结构 这些都是计算机网络中的内容&#xff0c;我们在这里简单回顾一下&#xff1a; UDP(User Datagram Protocol):用户数据报协议;TCP(Transmission Contr…

FastDFS - 无法获取服务端连接资源:can‘t create connection to/xx.xx.xx.xx:0

问题描述 根据官方文档 安装完FastDFS服务器后&#xff0c; 服务正常启动&#xff0c;但是在 SpringBoot 项目使用 fastdfs-client 客户端报错无法获取服务端连接资源&#xff1a;cant create connection to/xx.xx.xx.xx:0, 一系列排查发现是获取到的 tracker 端口为 0 。 co…

开源模型应用落地-CodeQwen模型小试-SQL专家测试(二)

一、前言 代码专家模型是基于人工智能的先进技术&#xff0c;它能够自动分析和理解大量的代码库&#xff0c;并从中学习常见的编码模式和最佳实践。这种模型可以提供准确而高效的代码建议&#xff0c;帮助开发人员在编写代码时避免常见的错误和陷阱。 通过学习代码专家模型&…