🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:“不要等到什么都没有了,才下定决心去做”
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
封装线程
Linux线程互斥
加锁
创建一个锁(互斥量)
pthread_mutex_lock(&mutex);加锁/pthread_mutex_unlock(&mutex);解锁
源码实现
封装线程
//开发方 #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++;
这种操作不是原子的
抢票中的临界区
抢票中的临界区 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上阻塞,(等待其他线程释放掉锁,再被唤醒申请锁)
申请锁本身是安全的,原子的(只能一个线程申请成功)
一个线程在临界区中访问临界资源的时候,可不可能发生切换?
可能,完全允许,因为该线程被切换,但是没有解锁,其他线程就申请不到锁,只能被阻塞(就不会发生并发访问数据不一致的问题)
创建一个锁(互斥量)
pthread_mutex_t mutex;
pthread_mutex_lock(&mutex);加锁/pthread_mutex_unlock(&mutex);解锁
在Linux系统中,pthread_mutex_lock 是一个 POSIX 线程库提供的函数,用于加锁互斥量(mutex)。它的作用是尝试锁定一个互斥量,如果这个互斥量已经被其他线程锁定了,则调用线程会被阻塞直到获取到该互斥量为止。
事例
#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
#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
#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只是退出主线程,并不会导致进程的退出
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸