【Linux】线程互斥 |
您所在的位置:网站首页 › pthread_cond_init › 【Linux】线程互斥 |
🌠 作者:@阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
出现问题的原因: tickets > 0 判断的本质也是计算,计算就需要将内存中的数据读取到 CPU 的寄存器中,而将数据读取到 CPU 的寄存器中的本质就是将数据读取到当前执行流的上下文中。这样就有可能出现多个执行流进行判断是都没有小于 0,符合判断条件,然后多个执行流进行减减操作将 tickets 减到负数,那么就出现了 tickets 出现负数的情况了。除了这个原因,还会因为调度时序的问题,导致数据不一样的问题!
那如何解决这个问题呢?为了解决这个问题,我们需要进行加锁保护! 互斥量要解决上面的问题,需要做到一下三点: 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。要做到这三点,本质上就是需要一把锁,Linux 系统提供的这把锁叫互斥量。
调用 pthread_mutex_lock 时,可能会遇到以下情况: 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么 pthread_mutex_lock 调用会陷入阻塞(执行流被挂起),等待互斥量解锁。注:pthread_mutex_trylock 申请锁时,如果申请成功,则返回 0;如果申请失败,则立即返回错误码,并不会阻塞等待锁释放。 全局互斥锁 #include #include #include #include using namespace std; // pthread_mutex_t是原生线程库提供的数据类型 pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; // 全局的互斥锁 int tickets = 10000; // 临界资源 void* getTickets(void* args) { (void)args; while(true) { // 只有获得锁的线程才能执行进入临界区,访问临界资源 pthread_mutex_lock(&mtx); // 加锁保护 // 加锁和解锁之间的代码就是临界区 if(tickets > 0) { usleep(1000); printf("%p: %d\n", pthread_self(), tickets--); pthread_mutex_unlock(&mtx); // 解锁(释放锁) } else { pthread_mutex_unlock(&mtx); // 解锁(释放锁) break; } // 不能在这里解锁,因为tickets小于0,会跳出while循环 // 无法执行到这里,从而导致锁没有释放,其余线程无法获 // 得锁而阻塞等待 // 抢完票还需要后续的动作 } return nullptr; } int main() { // 多线程抢票 pthread_t t1, t2, t3; pthread_create(&t1, nullptr, getTickets, nullptr); pthread_create(&t2, nullptr, getTickets, nullptr); pthread_create(&t3, nullptr, getTickets, nullptr); pthread_join(t1, nullptr); pthread_join(t2, nullptr); pthread_join(t3, nullptr); return 0; }如果出现票都被一个线程抢到的情况,可以将票数增多一点或者休眠时间随机一点,以让每个线程都能够抢到票。
局部互斥锁 #include #include #include #include #include #include #include using namespace std; #define THREAD_NUM 5 // 线程数量 class ThreadData { public: ThreadData(const string& tname, pthread_mutex_t* pmtx) : _tname(tname) , _pmtx(pmtx) {} string _tname; pthread_mutex_t* _pmtx; }; int tickets = 10000; // 临界资源 void* getTickets(void* args) { ThreadData* td = (ThreadData*)args; while(true) { // 只有获得锁的线程才能执行进入临界区,访问临界资源 int n = pthread_mutex_lock(td->_pmtx); // 加锁 assert(n == 0); (void)n; // 加锁和解锁之间的代码就是临界区 if(tickets > 0) { usleep(rand() % 2000); printf("%s: %d\n", td->_tname.c_str(), tickets--); pthread_mutex_unlock(td->_pmtx); // 解锁 } else { pthread_mutex_unlock(td->_pmtx); break; } // 抢到票后的其它逻辑 usleep(rand() % 2000); } delete td; // 票数为0时,释放ThreadData return nullptr; } int main() { int begin = time(nullptr); pthread_mutex_t mtx; // 第二个参数为锁的属性,设置为nullptr表示不关心 pthread_mutex_init(&mtx, nullptr); // 初始化锁 pthread_t t[THREAD_NUM]; for(int i = 0; i pthread_join(t[i], nullptr); } pthread_mutex_destroy(&mtx); // 销毁锁 int end = time(nullptr); // 输出抢票所消耗的时间 cout } string _name; func_t _func; pthread_mutex_t* _pmtx; pthread_cond_t* _pcon; }; void func1(const string& name, pthread_mutex_t* pmtx, pthread_cond_t* pcon) { while(!quit) { // 检测临界资源是否就绪,需要在加锁和解锁之间检测 // wait等待一定要在加锁和解锁之间进行 pthread_mutex_lock(pmtx); // if(临界资源是否就绪 - 否) pthread_cond_wait(pcon, pmtx); // wait代码被执行,当前线程会被立即被阻塞,也就是线程的状态变成了S,放入到等待队列中 // 线程被唤醒时,会有一定的唤醒顺序 pthread_cond_wait(pcon, pmtx); // 现在不需要关心pmtx,后续会讲解 cout // wait等待一定要在加锁和解锁之间进行 pthread_mutex_lock(pmtx); // wait代码被执行,当前线程会被立即被阻塞 pthread_cond_wait(pcon, pmtx); cout // wait等待一定要在加锁和解锁之间进行 pthread_mutex_lock(pmtx); // wait代码被执行,当前线程会被立即被阻塞 pthread_cond_wait(pcon, pmtx); cout pthread_mutex_t mtx; pthread_cond_t cond; pthread_mutex_init(&mtx, nullptr); pthread_cond_init(&cond, nullptr); pthread_t tids[THREAD_NUM]; func_t funcs[THREAD_NUM] = {func1, func2, func3}; for(int i = 0; i cout |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |