线程的互斥
见一见
见一见多线程访问的问题--抢票代码。复用线程控制的.hpp代码,只需修改main.cc代码
#include <iostream>
#include <unistd.h>
#include <vector>
#include "thread.hpp"
using namespace std;
int tickets = 10000;//共享资源,造成了资源不一致的问题
using namespace threadmodel; // 使用自定义的命名空间
void route(const string &name)
{
while (true)
{
// cout << name << "is running " << endl;
// sleep(1);
if (tickets > 0)
{ // 有票才能抢
usleep(1000); // 1ms->抢票花费的时间
printf("who: %s,get a ticket:%d\n",name.c_str(),tickets);//%s转化c风格
tickets--;
}
else
{
break;
}
}
}
int main()
{
thread t1("thread-1", route);
thread t2("thread-2", route);
thread t3("thread-3", route);
thread t4("thread-4", route);
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
}
1. 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
2. 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
3. 多个线程并发的操作共享变量,会带来一些问题。
问题
认识和分析为什么会出现抢到负数的问题
如何解决呢?加锁
认识锁和他的接口
Ubuntu 本身没有“锁”的函数
所以从其他系统里找
未来呢,多线程都会统一的访问共享资源,我们要把这个资源进行保护,这个保护的资源就叫临界资源 ;我们把访问临界资源的代码叫做临界区;其他的代码不属于访问共享资源的代码叫做非临界区;
所谓的对临界资源的保护,本质是对临界区代码进行保护;我们对所有资源进行访问,本质都是通过代码进行访问的;所以保护资源,本质就是想办法把访问资源的 代码保护起来;
1. 加锁的范围,粒度一定要小 ;
2. 任何线程,要进行抢票(临界区)都得先申请锁,原则上不应该有例外;3. 所有线程申请锁,前提是所有线程都得看到这把锁,锁本身也是共享资源--加锁的过程必须是原子的;
4. 原子性:要么不做,要做就昨完,没有中间状态,就是原子性(要么抢到要么不抢,没有正在抢);
5. 如果线程申请锁失败了,那么我的线程就会阻塞,直到有人把锁释放了我才会唤醒区重新竞争这把锁;
6. 如果线程申请成功了,会继续向后运行;
7. 如果线程申请成功了,执行临界区的代码,执行临界区代码期间,可以切换么?可以的,因为在cpu眼里没有区别,都是代码。那么一个线程在临界区被切换了,那么其他线程无法进入临界区,因为虽然被切换了但是我被加锁了还没解锁了--我可以放心地执行,无人打扰;
结论:所以对于其他线程,要么我没有申请锁,要么我释放了锁,对其他线程才有意义!------我访问临界区,对其他线程是原子的!
#include <iostream>
#include <unistd.h>
#include <vector>
#include "thread.hpp"
using namespace std;
int tickets = 10000; // 共享资源,造成了资源不一致的问题
using namespace threadmodel; // 使用自定义的命名空间
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER; // 先定义一个全局的锁
void route(const string &name)
{
while (true)
{
pthread_mutex_lock(&gmutex); // 加锁
if (tickets > 0)
{ // 有票才能抢
usleep(1000); // 1ms->抢票花费的时间
printf("who: %s,get a ticket:%d\n", name.c_str(), tickets); //%s转化c风格
tickets--;
pthread_mutex_unlock(&gmutex); // 解锁,不解锁其他线程进不来
}
else
{
pthread_mutex_unlock(&gmutex); // 解锁,不解锁其他线程进不来
break;
}
}
}
int main()
{
thread t1("thread-1", route);
thread t2("thread-2", route);
thread t3("thread-3", route);
thread t4("thread-4", route);
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
}
局部性的锁,让全部线程看到
main.cc:#include <iostream> #include <unistd.h> #include <vector> #include "thread.hpp" using namespace std; int tickets = 10000; // 共享资源,造成了资源不一致的问题 using namespace threadmodel; // 使用自定义的命名空间 //pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER; // 先定义一个全局的锁 void route(threaddate *td) { while (true) { pthread_mutex_lock(td->_lock); // 加自己的锁,以参数的形式传递 if (tickets > 0) { // 有票才能抢 usleep(1000); // 1ms->抢票花费的时间 printf("who: %s,get a ticket:%d\n", td->_name.c_str(), tickets); //%s转化c风格 tickets--; pthread_mutex_unlock(td->_lock); // 解锁,不解锁其他线程进不来 } else { pthread_mutex_unlock(td->_lock); // 解锁,不解锁其他线程进不来 break; } } } static int num=4; int main() { pthread_mutex_t mutex;//定义局部的锁 pthread_mutex_init(&mutex,nullptr);//初始化 vector<thread>threads; for(int i=0;i<num;i++){//线程创建 string name="thread-"+to_string(i+1); threaddate*td=new threaddate(name,&mutex);//传递给每一个线程 threads.emplace_back(name,route,td); } for(auto &thread:threads){ thread.start(); } for(auto &thread:threads){ thread.join(); } pthread_mutex_destroy(&mutex);//释放锁 return 0; }
thread.hpp:
#pragma once #include <iostream> #include <string> #include <pthread.h> #include <functional> //回调方法 using namespace std; namespace threadmodel // 构造一个命名空间 { class threaddate { public: threaddate(string &name, pthread_mutex_t *lock) : _name(name), _lock(lock) {} public: string _name;//线程名 pthread_mutex_t *_lock;//锁 }; typedef void (*func_t)(threaddate *td); // typedef void (*func_t)(const string &name); // func_t 是一个指向返回类型为 void 且有参数的函数的指针。 // // func_t 现在可以用作一个函数指针类型的别名,表示任何指向带字符串参数且返回类型为 void 的函数的指针。 class thread { public: void excute() { cout << _name << " is running ! " << endl; _running = true; _func(_td); // 回调不仅回调运行,他还得结束 _running = false; // 回调完就结束了 } public: thread(const string &name, func_t func, threaddate *td) : _name(name), _func(func), _td(td) { cout << "create: " << name << " done! " << endl; } static void *threadroutine(void *agv) // 只要线程启动,新线程都会启动这个方法 { // 因为是类内定义的方法,所以会隐含一个this指针参数,加了static就可以,这是因为 static 成员函数不与类的实例相关联,因此它不需要 this 指针。 thread *self = static_cast<thread *>(agv); // 获得当前对象。因为要调用_func,但_func是动态的,静态函数无法访问所以传this指针访问 self->excute(); return nullptr; } bool start() { // 线程启动方法 int n = ::pthread_create(&_tid, nullptr, threadroutine, this); // 使用::pthread_create确保调用的是全局命名空间中的pthread_create函数,避免当前命名空间内可能存在的同名函数的影响。 // 直接使用 pthread_create 会根据当前命名空间查找,如果找到了同名函数,就会调用那个函数。 if (n != 0) return false; return true; } string status() { if (_running) { return "running"; } else return "sleep"; } void stop() { // 线程停止方法 if (_running) { // 得先有线程才能停止 _running = false; // 状态停止 ::pthread_cancel(_tid); cout << _name << " stop ! " << endl; } } void join() { // 线程等待方法 if (!_running) { // 没有running才值得join ::pthread_join(_tid, nullptr); cout << _name << " join ! " << endl; } delete _td; } string threadname() { return _name; } ~thread() { } private: string _name; // 线程的名字 pthread_t _tid; // 线程的id bool _running; // 是否处于工作状态 func_t _func; // 线程要执行的回调函数 threaddate *_td;//线程的参数 }; };
解决历史问题
原理角度理解这个锁
从实现角度理解锁
原文地址:https://blog.csdn.net/yiqizhuashuimub/article/details/142904289
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!