Linux中的多线程

news2024/10/6 0:57:22

Linux线程概念

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序 列”

进程是系统分配资源的基本实体

线程是CPU调度的基本单位

POSIX线程库

创建线程

功能:创建一个新的线程
原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
 thread:返回线程ID
 attr:设置线程的属性,attr为NULL表示使用默认属性
 start_routine:是个函数地址,线程启动后要执行的函数
 arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

ps -aL

查看所有执行流

两个进程pid相同,但lwp不同,lwp->(light weight process)轻量级进程

线程的私有资源

1.PCB属性私有

2.有一定的私有上下文结构

3.独立的栈结构

线程对应的函数运行完成后,线程也会结束。

线程等待

线程等待 为什么需要线程等待?

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

创建新的线程不会复用刚才退出线程的地址空间。

功能:等待线程结束
原型
 int pthread_join(pthread_t thread, void **value_ptr);
参数
 thread:线程ID
 value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

线程终止

功能:线程终止
原型
 void pthread_exit(void *value_ptr);
参数
 value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};
void* start_routine(void* args)//可重入函数
void* start_routine(void* args)
{//可重入函数
    ThreadData* td=static_cast<ThreadData*>(args);
    int cnt=10;
    while (cnt)
    {
        //cout<<"new thread create success, name: "<<td->namebuffer<<endl;
        cout<<"cnt:  "<<cnt<<"   &cnt:  "<<&cnt<<endl;
        cnt--;
        sleep(1);
    }
    //delete td;
    pthread_exit(nullptr);
    //return nullptr;
}
vector<ThreadData*> threads;
#define NUM 10
    for(int i=0;i<NUM;i++)
    {
        ThreadData* td= new ThreadData();
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s : %d","thread",i+1);
        pthread_create(&td->tid,nullptr,start_routine,td);
        threads.push_back(td);
    }
    for(auto& iter: threads)
    {
        cout<<"create thread: "<<iter->namebuffer<<" : "<<iter->tid<<"  success"<<endl;

    }
    for(auto& iter:threads)
    {
        int n=pthread_join(iter->tid,nullptr);
        assert(n==0);
        cout<<"join : "<<iter->namebuffer<<" success"<<endl;
        delete iter;
    }
    cout<<"main thread quit"<<endl;

void* start_routine(void* args)
{//可重入函数
    ThreadData* td=static_cast<ThreadData*>(args);
    int cnt=10;
    while (cnt)
    {
        //cout<<"new thread create success, name: "<<td->namebuffer<<endl;
        cout<<"cnt:  "<<cnt<<"   &cnt:  "<<&cnt<<endl;
        cnt--;
        sleep(1);
    }
    //delete td;
    pthread_exit(nullptr);
    //return nullptr;
    return (void*)td->number;
}
for(auto& iter:threads)
    {
        void* ret=nullptr;
        int n=pthread_join(iter->tid,&ret);//void** retp; *retp=(void*)td->number
        assert(n==0);
        cout<<"join : "<<iter->namebuffer<<" success,number: "<<(long long)ret<<endl;
        delete iter;
    }
    cout<<"main thread quit"<<endl;

线程异常,收到信号,整个进程都会退出。

线程被取消其退出码为-1(PTHREAD_CANCELED)

pthread_t tid;
    pthread_create(&tid,nullptr,start_routine,(void*)"thread1");
    string main_id = changeId(pthread_self());
    pthread_detach(tid);

在主线程中将新线程分离

int g_val=100;
string changeId(const pthread_t& thread_id)
{
    char tid[120];
    snprintf(tid,sizeof tid,"  0x%lx",thread_id);
    return tid;
}
void* start_routine(void* args)
{
    string threadname=static_cast<const char*>(args);
    int cnt=5;
    while(true)
    {
        cout<<threadname<<"running ..."<<changeId(pthread_self())<<"   g_val: "<<g_val<<"    &g_val: "<<&g_val<<endl;
        g_val++;
        sleep(1);
    }
}
pthread_t tid;
    pthread_create(&tid,nullptr,start_routine,(void*)"thread1");
    string main_id = changeId(pthread_self());
    pthread_detach(tid);

    cout<<"main thread running, ...new thread id: "<<changeId(tid)<<"main thread id: "<<main_id<<endl;
    while(true)
    {
        cout<<"main thread running... new thread id: "<<changeId(tid)
            <<"main thread id: "<<main_id<<"  g_val: "<<g_val<<"  &g_val: "
                <<&g_val<<endl;
        sleep(1);
    }
__thread int g_val=100;

添加__thread ,可以将一个内置类型设为线程局部储存,给每个线程各一份

封装thread

#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include<functional>
#include<pthread.h>

using namespace std;

class Thread;
class Context
{
public:
    Thread* this_;
    void* args_;
public:
    Context()
        :this_(nullptr),args_(nullptr)
    {}
    ~Context()
    {}
};

class Thread
{
public:
    typedef function<void*(void*)> func_t;
    const int num =1024;
public:
    Thread(func_t func,void* args,int number)
        :func_(func),args_(args)
    {
        char buffer[num];
        snprintf(buffer,sizeof buffer,"thread-%d",number);
        name_=buffer;
        Context* ctx=new Context();
        ctx->this_=this;
        ctx->args_=args_;
        int n=pthread_create(&tid_,nullptr,start_routine,ctx);
    }
    static void* start_routine(void* args)
    {
        Context* ctx=static_cast<Context*>(args);
        void* ret=ctx->this_->run(ctx->args_);
        delete ctx;
        return ret;
    }

    void join()
    {
        int n=pthread_join(tid_,nullptr);
    }
    void* run(void* args)
    {
        return func_(args);
    }
    ~Thread()
    {}
private:
    string name_;
    pthread_t tid_;
    func_t func_;
    void* args_;
};
void* thread_run(void* args)
{
    string work_type=static_cast<const char*>(args);
    while(true)
    {
        cout<<"我是一个新线程,我正在作: "<<work_type<<endl;
        sleep(1);
    }
}

    unique_ptr<Thread> thread1(new Thread(thread_run,(void*)"hellothread",1));
    unique_ptr<Thread> thread2(new Thread(thread_run,(void*)"hellothread",2));
    unique_ptr<Thread> thread3(new Thread(thread_run,(void*)"hellothread",3));
    thread1->join();
    thread2->join();
    thread3->join();

互斥量的接口

初始化互斥量

初始化互斥量有两种方法:

方法1,静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER 

方法2,动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict 
attr); 
 参数: 
 mutex:要初始化的互斥量 
 attr:NULL

销毁互斥量

销毁互斥量需要注意:

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

不要销毁一个已经加锁的互斥量

已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex); 
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
返回值:成功返回0,失败返回错误号 

单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题

大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单 元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一 个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

共识:

1.CPU内寄存器只有一套被所有的执行流共享

2.CPU内寄存器的内容是每个执行流私有的->运行时上下文

封装mutex

#pragma once

#include<iostream>
#include<pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock_p=nullptr)
        :lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_) pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_) pthread_mutex_unlock(lock_p_);
    }
    ~Mutex()
    {} 
private:
    pthread_mutex_t* lock_p_;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* mutex)
        :mutex_(mutex)
    {
        mutex_.lock();
    }
    ~LockGuard()
    {
        mutex_.unlock();
    }
private:
    Mutex mutex_;
};

死锁

死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

Linux线程同步

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情 况就需要用到条件变量。

条件变量函数 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict 
attr); 
参数: 
 cond:要初始化的条件变量 
 attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 
 参数: 
 cond:要在这个条件变量上等待 
 mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond); 
int pthread_cond_signal(pthread_cond_t *cond); 
#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>

using namespace std;
int tickets=1000;
pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void* start_routine(void* args)
{
    char* name= static_cast<char*>(args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        cout<<name<<" -> "<<tickets<<endl;
        tickets--;
        pthread_mutex_unlock(&mutex);
    }
    
}
int main()
{
    //通过条件变量控制线程执行
    pthread_t t[5];
    for(int i=0;i<5;i++)
    {
        char* name= new char[64];
        snprintf(name,64,"thread: %d",i+1);
        pthread_create(t+i,nullptr,start_routine,name);
    }
    
    while(true)
    {
        sleep(1);
        pthread_cond_signal(&cond);//唤醒条件变量下的线程
    }
    for(int i=0;i<5;i++)
    {
        pthread_join(t[i],nullptr);
    }
    
    return 0;
}

 

生产者消费者模型

总结(321原则):

3种关系:

生产者和生产者(互斥)

消费者和消费者(互斥)

生产者和消费者(互斥->保证共享资源的安全性,同步)

2种角色:

生产者线程,消费者线程

1个交易场所:

一段特点结构的缓存区

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

执行路径带空格的服务漏洞

原理 当系统管理员配置Windows服务时&#xff0c;必须指定要执行的命令&#xff0c;或者运行可执行文件的路径。 当Windows服务运行时&#xff0c;会发生以下两种情况之一。 1、如果给出了可执行文件&#xff0c;并且引用了完整路径&#xff0c;则系统会按字面解释它并执行 …

算法修炼之路之滑动窗口

目录 一&#xff1a;滑动窗口的认识及模板 二&#xff1a;LeetcodeOJ练习 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 一&#xff1a;滑动窗口的认识及模板 这里先通过一道题来引出滑动窗口 LeetCode 209 长度最小的子数组 画图分析&…

软件验证与确认实验一:静态分析

目录 1. 实验目的及要求.................................................................................................... 3 2. 实验软硬件环境.................................................................................................... 3 …

(C语言贪吃蛇)15.贪吃蛇吃食物

目录 前言 注意事项⚠️ 效果预览 实现方法 运行效果 新的问题&#x1f64b; 最终效果 总结 前言 我们上一节实现了解决了贪吃蛇不合理走位的情况&#xff0c;不理解的再回去看看(传送门&#xff1a;解决贪吃蛇不合理走位)&#xff0c;那么贪吃蛇自然是要吃食物的啊&…

springboot系列--web相关知识探索四

一、前言 web相关知识探索三中研究了请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。web相关知识探索三中主要研究了注解方式以及Servlet API方式。本次…

基于springboot vue 电影推荐系统

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…

DatePicker 日期控件

效果&#xff1a; 要求&#xff1a;初始显示系统当前时间&#xff0c;点击日期控件后修改文本控件时间。 目录结构&#xff1a; activity_main.xml(布局文件)代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:and…

环境可靠性

一、基础知识 1.1 可靠性定义 可靠性是指产品在规定的条件下、在规定的时间内完成规定的功能的能力。 可靠性的三大要素&#xff1a;耐久性、可维修性、设计可靠性 耐久性&#xff1a;指的是产品能够持续使用而不会故障的特性&#xff0c;或者说是产品的使用寿命。 可维修性&a…

1.MySQL存储过程基础(1/10)

引言 数据库管理系统&#xff08;Database Management System, DBMS&#xff09;是现代信息技术中不可或缺的一部分。它提供了一种系统化的方法来创建、检索、更新和管理数据。DBMS的重要性体现在以下几个方面&#xff1a; 数据组织&#xff1a;DBMS 允许数据以结构化的方式存…

【C++ STL】手撕vector,深入理解vector的底层

vector的模拟实现 前言一.默认成员函数1.1常用的构造函数1.1.1默认构造函数1.1.2 n个 val值的构造函数1.1.3 迭代器区间构造1.1.4 initializer_list 的构造 1.2析构函数1.3拷贝构造函数1.4赋值运算符重载 二.元素的插入,删除,查找操作2.1 operator[]重载函数2.2 push_back函数:…

读论文、学习时 零碎知识点记录01

1.入侵检测技术 2.深度学习、机器学习相关的概念 ❶注意力机制 ❷池化 ❸全连接层 ❹Dropout层 ❺全局平均池化 3.神经网络中常见的层

51c视觉~CV~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/11668984 一、 CV确定对象的方向 介绍如何使用OpenCV确定对象的方向(即旋转角度&#xff0c;以度为单位)。 先决条件 安装Python3.7或者更高版本。可以参考下文链接&#xff1a; https://automaticaddison.com/how-to-s…

【2024年最新】基于springboot+vue的毕业生信息招聘平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

基于keras的停车场车位识别

1. 项目简介 该项目旨在利用深度学习模型与计算机视觉技术&#xff0c;对停车场中的车位进行检测和状态分类&#xff0c;从而实现智能停车管理系统的功能。随着城市化的发展&#xff0c;停车场管理面临着车位检测效率低、停车资源分配不均等问题&#xff0c;而传统的人工检测方…

【Python】Dejavu:Python 音频指纹识别库详解

Dejavu 是一个基于 Python 实现的开源音频指纹识别库&#xff0c;主要用于音频文件的识别和匹配。它通过生成音频文件的唯一“指纹”并将其存储在数据库中&#xff0c;来实现音频的快速匹配。Dejavu 的主要应用场景包括识别音乐、歌曲匹配、版权管理等。 ⭕️宇宙起点 &#x1…

【AI知识点】泊松分布(Poisson Distribution)

泊松分布&#xff08;Poisson Distribution&#xff09; 是统计学和概率论中的一种离散概率分布&#xff0c;通常用于描述在固定时间或空间内&#xff0c;某个事件发生的次数。该分布适用于稀有事件的建模&#xff0c;特别是当事件发生是独立的、随机的&#xff0c;且发生的平均…

[Go语言快速上手]初识Go语言

目录 一、什么是Go语言 二、第一段Go程序 1、Go语言结构 注意 2、Go基础语法 关键字 运算符优先级 三、Go语言数据类型 示例 小结 一、什么是Go语言 Go语言&#xff0c;通常被称为Golang&#xff0c;是一种静态类型、编译型的计算机编程语言。它由Google的Robert Gr…

关闭IDM自动更新

关闭IDM自动更新 1 打开注册表2 找到IDM注册表路径 1 打开注册表 winR regedit 2 找到IDM注册表路径 计算机\HKEY_CURRENT_USER\Software\DownloadManager 双击LstCheck&#xff0c;把数值数据改为0 完成 感谢阅读

存储电话号码的数据类型,用 int 还是用 string?

在 Java 编程中&#xff0c;存储电话号码的选择可以通过两种常见方式进行&#xff1a;使用 int 类型或 String 类型。这种选择看似简单&#xff0c;但实际上涉及到 JVM 内部的字节码实现、内存优化、数据表示、以及潜在的可扩展性问题。 Java 基本数据类型与引用数据类型的差异…

Windows安全加固详解

一、补丁管理 使用适当的命令或工具&#xff0c;检查系统中是否有未安装的更新补丁。 Systeminfo 尝试手动安装一个系统更新补丁。 • 下载适当的补丁文件。 • 打开命令提示符或PowerShell&#xff0c;并运行 wusa.exe <patch_file_name>.msu。 二、账号管…