多线程详解

目录

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.mfbz.cn/a/595457.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年电化学、可再生能源与绿色发展国际会议(ICERGD2024)

2024年电化学、可再生能源与绿色发展国际会议(ICERGD2024) 会议简介 2024国际电化学、可再生能源与绿色发展大会&#xff08;ICERGD2024&#xff09;将在青岛隆重举行。本次会议聚焦电化学、可再生能源和绿色发展领域的最新研究成果和技术趋势&#xff0c;旨在促进相关领域…

如何快速找出文件夹里的全部带有数字纯数字的文件

参考此文章&#xff1a;如何快速找出文件夹里的全部带有中文&纯中文的文件 只需要根据自己的需求&#xff0c;把下面相关的设置调整好即可

【电商-虾皮】

电商-虾皮 ■ 人口分布■ 市场■ 欧美市场■ 东南亚市场 ■ ■ 人口分布 ■ 市场 ■ 欧美市场 亚马逊 ■ 东南亚市场 shopee ■

如何通过前端表格控件在10分钟内完成一张分组报表?

前言&#xff1a; 当今时代&#xff0c;报表作为信息化系统的重要组成部分&#xff0c;在日常的使用中发挥着关键作用。借助报表工具使得数据录入、分析和传递的过程被数字化和智能化&#xff0c;大大提高了数据的准确性及利用的高效性。而在此过程中&#xff0c;信息化系统能…

【前端学习——防抖和节流+案例】

定义 【前端八股文】节流和防抖 防抖 连续触发事件但是在设定的一段时间内只执行最后一次 代码实现思路【定时器】 大概意思就是&#xff1a; 每次按起键盘后&#xff0c;都将之前的定时器删除&#xff0c;重新开始计时。 节流 连续触发事件&#xff0c;只执行一次 …

【005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放】

005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放 文章目录 005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放创作背景MA120X0P输出模式BTLSEPBTLSEBTL 硬件配置方式/硬件Limiter限幅器限幅器作用过程 主要寄存器操作指令 ma120x0p.cma120x0p.h 创作背景 学历代表过去、能…

node应用部署运行案例

生产环境: 系统&#xff1a;linux centos 7.9 node版本&#xff1a;v16.14.0 npm版本:8.3.1 node应用程序结构 [rootRainYun-Q7c3pCXM wiki]# dir assets config.yml data LICENSE node_modules nohup.out output.log package.json server wiki.log [rootRainYun-Q7c…

Coursera: An Introduction to American Law 学习笔记 Week 06: Civil Procedure (完结)

An Introduction to American Law Course Certificate Course Introduction 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 06: Civil Pro…

伙伴匹配(后端)-- 组队功能

文章目录 需求分析数据库表设计基础接口开发系统设计及开发创建队伍业务逻辑细化业务层代码优化完成控制层 查询队伍列表业务逻辑vo层业务层代码接口代码 修改队伍信息业务逻辑同样在请求包里封装一个用户登录请求体接口修改业务实现类 用户可以加入队伍同样在请求包里封装一个…

电脑问题2【彻底删除CompatTelRunner】

彻底删除CompatTelRunner 电脑偶尔会运行CompatTelRunner造成CPU占用的资源非常大,所以这里要想办法彻底关闭他 本文摘录于&#xff1a;https://mwell.tech/archives/539只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 解决办法是进入W…

WinForm DataGridView 垂直滑动条显示异常

WinForm DataGridView的垂直滑动条不正常显示&#xff0c;当总行高超过控件高度&#xff08;控件高度为227及以下不会出现该问题&#xff09;时&#xff0c;右下角会出现一个灰框&#xff0c;因为表格控件位处TabControl下&#xff0c;当切换其他选项卡后再切回来时&#xff0c…

Python类方法探秘:从单例模式到版本控制

引言&#xff1a; 在Python编程中&#xff0c;类方法作为一种特殊的实例方法&#xff0c;以其独特的魅力在众多编程范式中脱颖而出。它们不仅提供了无需实例即可调用的便捷性&#xff0c;还在设计模式、版本控制等方面发挥着重要作用。本文将通过几个生动的示例&#xff0c;带您…

RS2057XH功能和参数介绍及规格书

RS2057XH 是一款由润石科技&#xff08;Runic Semiconductor&#xff09;生产的模拟开关芯片&#xff0c;其主要功能和参数如下&#xff1a; 产品特点&#xff1a; 低电压操作&#xff1a;支持低至1.8V的工作电压&#xff0c;适用于低功耗应用。 高带宽&#xff1a;具有300MHz的…

库存管理方法有哪些?

库存管理对于企业的成本控制、运营效率及市场竞争力至关重要。有效的库存管理能够确保生产连续、降低成本、提升效率&#xff0c;进而增强企业市场竞争力。本文旨在介绍几种主流的库存管理方法&#xff0c;包括高效利用仓库管理软件&#xff0c;定量库存管理法、定期库存管理法…

配电网变压器容量选择与变损计算方法及python简易实现

1. 配电网变压器容量选择方法 1.1. 配电网变压器容量选择方法 在选择变压器容量时&#xff0c;需要考虑的最大因素是负荷的峰值&#xff08;或称为最大需求&#xff09;&#xff0c;同时也要考虑变压器的效率、预期负载系数&#xff08;负载占额定容量的比例&#xff09;、以…

2024数维杯数学建模B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

multipass launch失败:launch failed: Remote ““ is unknown or unreachable.

具体问题情况如下&#xff1a; C:\WINDOWS\system32>multipass launch --name my-vm 20.04launch failed: Remote "" is unknown or unreachable.​C:\WINDOWS\system32>multipass lsNo instances found.​C:\WINDOWS\system32>multipass startlaunch fail…

Linux 磁盘管理命令fdisk mount umount mkfs mkfs.ext2

文章目录 3.Linux 磁盘管理命令3.4 fdisk&#xff1a;磁盘分区案例练习 3.5 mount&#xff1a;挂载文件系统案例练习 3.6 umount&#xff1a;卸载文件系统案例练习 3.7 mkfs&#xff1a;建立各种文件系统案例练习 3.8 mkfs.ext2&#xff1a;建立一个 Ext2/Ext3 文件系统案例练习…

深度学习中的归一化:BN,LN,IN,GN的优缺点

目录 深度学习中归一化的作用常见归一化的优缺点 深度学习中归一化的作用 加速训练过程 归一化可以加速深度学习模型的训练过程。通过调整输入数据的尺度&#xff0c;归一化有助于改善优化算法的收敛速度。这是因为归一化后的数据具有相似的尺度&#xff0c;使得梯度下降等优化…

Redis - Zset 有序集合

前言 它保留了集合不能有重复成员的特点&#xff0c;但与集合不同的是&#xff0c;有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数&#xff08;score&#xff09;与之关联&#xff0c;有序集合中的元素是可以维护有序性的&#xff0c;但这个有序不是⽤下标作为排序依据⽽是…
最新文章