Linux 第三十四章

🐶博主主页: @ᰔᩚ. 一怀明月ꦿ

❤️‍🔥专栏系列: 线性代数C初学者入门训练题解CC的使用文章「初学」C++linux

🔥座右铭:"不要等到什么都没有了,才下定决心去做"

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

封装线程

Linux线程互斥

加锁

创建一个锁(互斥量)

pthread_mutex_lock(&mutex);加锁/pthread_mutex_unlock(&mutex);解锁

源码实现


封装线程

cpp 复制代码
//开发方
#pragma once

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


using namespace std;



//typedef function<void()> func_t
template<class T>

using func_t=function<void(T)>;



template<class T>

class Thread

{
public:
Thread(func_t<T> func,const string& threadname,T data)

:_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data)
{}


static void* Threadroutine(void* args)//类内成员方法,其第一个参数是this指针,所以会导致编译错误

//这里使用static,让Thraedroutine成为类的方法,
{
(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;

}


bool Isrunning()

{
return _isrunning;

}


string Threadname()

{
return _threadname;

}
private:
pthread_t _tid;

string _threadname;

bool _isrunning;

func_t<T> _func;

T _data;

};

//应用方
#include<iostream>
#include"thread.hpp"
#include<unistd.h>
#include<vector>


void Print(int num)

{
while(num)
{
cout<<"hello world :"<<num--<<endl;
sleep(1);
}
}


string Getthreadname()

{
static int number=1;//全局变量

char name[64];

snprintf(name,sizeof(name),"Thread-%d",number++);
return name;

}






int main()

{
//**1**
// Thread t(Print,Getthreadname());
// cout<<"is thread running?"<<t.Isrunning()<<endl;
// t.Start();
// cout<<"is thread running?"<<t.Isrunning()<<endl;
// t.Join();


//**2**
// const int num=5;
// vector<Thread> threads;


// for(int i=0;i<num;i++)
// {
// Thread t(Print,Getthreadname());
// threads.push_back(t);
// }


// for( auto& thread:threads)
// {
// cout<<thread.Threadname()<<"is running: "<<thread.Isrunning()<<endl;
// }


// for(auto& thread:threads)
// {
// thread.Start();
// }


// for( auto& thread:threads)
// {
// cout<<thread.Threadname()<<"is running: "<<thread.Isrunning()<<endl;
// }


// //不让主线程退出,让主线程等待子线程
// for( auto& thread:threads)
// {
// thread.Join();
// }


//**3**


Thread<int> t(Print,Getthreadname(),10);

t.Start();
t.Join();


return 0;

}

Linux线程互斥

1.直接实验?(不确定性高)

多线程抢票的逻辑
抢票系统

Thread-4 get a ticket:0

Thread-1 get a ticket:-1

Thread-2 get a ticket:-2

票被抢到负数是不合理的

2.看到现象---输出概念

1)数据不一致,共享资源(票)

2)任何一个时刻,只允许一个线程正在访问的资源------临界资源(互斥)

3)我们把我们进程中访问临界资源的代码------临界区(被保护起来的重点区域)

4)互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

3.解释问题
int cnt=0;

cnt++;

这种操作不是原子的

抢票中的临界区

cpp 复制代码
抢票中的临界区
int ticket = 10000;
void Getticket(string name)
{
while (true)
{
if (ticket > 0)//判断ticket也是计算
{
usleep(1000); // 模拟抢票的话费时间
printf("%s get a ticket:%d \n", name.c_str(), ticket);
ticket---;//在汇编层面,会执行三步
//我的理解:读是在每个线程的上下文数据中,---是在内存中
}
else
break;
}
// 实际情况还有后续的动作
}

CPU的基本功能

算:算数运算

逻:逻辑运算

中:处理内外中断

控:控制单元

加锁

加锁(牺牲效率为代价的,解决安全性问题)

1.我们要尽可能的给少的代码加锁

2.一般加锁,都是给临界区加锁

根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁的失败,失败的线程怎么办?在mutex上阻塞,(等待其他线程释放掉锁,再被唤醒申请锁)

申请锁本身是安全的,原子的(只能一个线程申请成功)

一个线程在临界区中访问临界资源的时候,可不可能发生切换?

可能,完全允许,因为该线程被切换,但是没有解锁,其他线程就申请不到锁,只能被阻塞(就不会发生并发访问数据不一致的问题)

创建一个锁(互斥量)

cpp 复制代码
pthread_mutex_t mutex;

pthread_mutex_lock**(&mutex);**加锁/pthread_mutex_unlock(&mutex);解锁

在Linux系统中,pthread_mutex_lock 是一个 POSIX 线程库提供的函数,用于加锁互斥量(mutex)。它的作用是尝试锁定一个互斥量,如果这个互斥量已经被其他线程锁定了,则调用线程会被阻塞直到获取到该互斥量为止。

事例

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex;

int shared_data = 0;//公共资源

void* thread_function(void* arg) {
    // 加锁
    pthread_mutex_lock(&mutex);
    
    // 访问共享资源
    shared_data++;
    printf("Thread ID %ld, shared data: %d\n", pthread_self(), shared_data);
    
    // 解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    // 初始化互斥量
    if (pthread_mutex_init(&mutex, NULL) != 0) {
        perror("Mutex initialization failed");
        exit(EXIT_FAILURE);
    }
    
    // 创建线程
    pthread_create(&tid1, NULL, thread_function, NULL);
    pthread_create(&tid2, NULL, thread_function, NULL);
    
    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    // 销毁互斥量
    pthread_mutex_destroy(&mutex);
    
    return 0;
}
在上面的示例中,我们首先创建了一个互斥量 pthread_mutex_t mutex,并在主线程中初始化它。然后创建了两个线程,它们会执行 thread_function 函数。在 thread_function 中,通过调用 pthread_mutex_lock 来锁定互斥量,确保对 shared_data 的访问是互斥的,然后进行相应操作,最后再用 pthread_mutex_unlock 解锁。
这样就确保了在任意时刻只有一个线程能够访问 shared_data,避免了数据竞争问题。请注意,对于互斥量的使用需要谨慎处理,以避免死锁等问题。

源码实现

main

cpp 复制代码
#include <iostream>

#include "thread.hpp"

#include <unistd.h>

#include <vector>



void Print(int num)

{
while (num)

{
cout << "hello world :" << num-- << endl;

sleep(1);
}
}


string Getthreadname()

{
static int number = 1; // 全局变量

char name[64];

snprintf(name, sizeof(name), "Thread-%d", number++);

return name;

}


int ticket = 10000;



pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 锁有了,被定义初始化,这是一把全局的锁



// 加锁(牺牲效率为代价的,解决安全性问题)
// 1.我们要尽可能的给少的代码加锁
// 2.一般加锁,都是给临界区加锁
void Getticket(string name)

{
while (true)

{
// 加锁
// 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁的失败,失败的线程怎么办?在mutex上阻塞,(等待其他线程释放掉锁,再被唤醒申请锁)
pthread_mutex_lock(&mutex); // 申请锁本身是安全的,原子的(只能一个线程申请成功)

// 一个线程在临界区中访问临界资源的时候,可不可能发生切换?
// 可能,完全允许,因为该线程被切换,但是没有解锁,其他线程就申请不到锁,只能被阻塞
//
if (ticket > 0)

{
usleep(1000); // 模拟抢票的话费时间

printf("%s get a ticket:%d \n", name.c_str(), ticket);

ticket--;
// 解锁
pthread_mutex_unlock(&mutex);
}
else
{
// 解锁
pthread_mutex_unlock(&mutex);
break;
}
}
// 实际情况还有后续的动作
}


int main()

{
string name1 = Getthreadname();

Thread<string> t1(name1, Getticket, name1);



string name2 = Getthreadname();

Thread<string> t2(name2, Getticket, name2);



string name3 = Getthreadname();

Thread<string> t3(name3, Getticket, name3);



string name4 = Getthreadname();

Thread<string> t4(name4, Getticket, name4);



t1.Start();
t2.Start();
t3.Start();
t4.Start();


t1.Join();
t2.Join();
t3.Join();
t4.Join();
return 0;

}

thread.hpp

cpp 复制代码
#pragma once

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


using namespace std;



//typedef function<void()> func_t
template<class T>

using func_t=function<void(T)>;



template<class T>

class Thread

{
public:
Thread(const string& threadname,func_t<T> func,T data)

:_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data)
{}


static void* Threadroutine(void* args)//类内成员方法,其第一个参数是this指针,所以会导致编译错误

//这里使用static,让Thraedroutine成为类的方法,
{
(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;

}


bool Isrunning()

{
return _isrunning;

}


string Threadname()

{
return _threadname;

}
private:
pthread_t _tid;

string _threadname;

bool _isrunning;

func_t<T> _func;

T _data;

};

进程是资源的分配单位,所以线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配

pthread_self() 用于获取用户态线程的tid,而并非轻量级进程ID

主线程调用pthread_cancel(pthread_self())函数来退出自己, 则主线程对应的轻量级进程状态变更成为Z, 其他线程不受影响,这是正确的(正常情况下我们也不会这么做....)

主线程调用pthread_exit只是退出主线程,并不会导致进程的退出

🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸

相关推荐
Rain_Rong39 分钟前
linux检测硬盘
linux·运维·服务器
过过过呀Glik43 分钟前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
刚学HTML1 小时前
leetcode 05 回文字符串
算法·leetcode
蜀黍@猿2 小时前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧4092 小时前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
真真-真真2 小时前
WebXR
linux·运维·服务器
zh路西法2 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
AC使者2 小时前
#B1630. 数字走向4
算法
冠位观测者2 小时前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
轩辰~2 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议