
在这漫长的暑假,学习让我感到不再孤单,一起为了秋招努力吧!(有一位一起学习的朋友,最近拿到了腾讯的实习,好羡慕~)
目录
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
是不是感觉太官方了,我们来看一下稍微通俗的解释(方便将来面试来讲):

我们先不使用互斥锁,实现一个抢票逻辑看会出现什么问题。
代码测试:
test.cc
- #include <iostream>
- #include <pthread.h>
- #include <unistd.h>
-
- using namespace std;
-
- int tickes = 1000;
- void* ThreadRun(void* args)
- {
- int id = *(int*)args;
- delete (int*)args;
-
- while(true)
- {
- if(tickes > 0)
- {
- //抢票
- usleep(1000);
- cout << "我是[" << id <<"] 我要抢的票是: " << tickes << endl;
- tickes--;
- }
- else
- {
- break;
- }
- }
- }
-
- int main()
- {
- pthread_t tid[5];
- for(int i = 0; i < 5; i++)
- {
- int* id = new int(i);
- pthread_create(tid+i, nullptr, ThreadRun, (void*)id);
- }
- for(int i = 0; i < 5; i++)
- {
- pthread_join(tid[i], nullptr);
- }
- return 0;
- }
Makefile
- test:test.cc
- g++ -o $@ $^ -lpthread -std=c++11
- .PHONY:clean
- clean:
- rm -f test
运行结果:

这时候我们会发现,多个线程在抢票过程中,会抢到负数的情况,这就意味着票卖多了。这种情况是商家不愿意看到的。所以这段代码在多执行流下存在线程安全问题!

实际上ticket--并非原子性的,它实际上要进行计算是要经过多步的,在这期间就有可能被多个线程重入,导致可能出现问题。
如何解决上面的问题呢?对临界区进行加锁!
1、代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2、如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3、如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

pthread_mutex_t* restrict mutex:传入要初始化锁的地址
const pthread_mutexattr_t* restrict attr:我们不用关心,设置成nullptr

pthread_mutex_t* mutex:传入要释放锁的地址

定义在全局的或静态的锁,不用再手动初始化和销毁,直接用就可以了。

pthread_mutex_t* mutex:传入锁的地址进行上锁

pthread_mutex_t:传入锁的地址,给对应的锁解开
test.cc
- #include <iostream>
- #include <pthread.h>
- #include <unistd.h>
-
- using namespace std;
-
- class Ticket
- {
- public:
- Ticket()
- :_tickes(1000)
- {
- pthread_mutex_init(&_mtx, nullptr);
- }
- ~Ticket()
- {
- pthread_mutex_destroy(&_mtx);
- }
- bool GetTicket()
- {
- bool rets = true;//rets是每个线程私有的
-
- //对临界区进行加锁保护
- //执行这部分代码的执行流是互斥的,也是串行的
- pthread_mutex_lock(&_mtx);
- if(_tickes > 0)
- {
- //抢票
- usleep(1000);
- cout << "我是[" << pthread_self() << "] 我要抢的票是: " << _tickes << endl;
- _tickes--;
- }
- else
- {
- //没有票
- printf("票已经抢空了\n");
- rets = false;
- }
- pthread_mutex_unlock(&_mtx);
-
- return rets;
- }
- private:
- int _tickes;
- pthread_mutex_t _mtx;
- };
-
- void* ThreadRun(void* args)
- {
- Ticket* t = (Ticket*)args;
-
- while(true)
- {
- if(!t->GetTicket())
- {
- break;
- }
- }
- }
-
- int main()
- {
- Ticket* t = new Ticket;
-
- pthread_t tid[5];
- for(int i = 0; i < 5; i++)
- {
- int* id = new int(i);
- pthread_create(tid+i, nullptr, ThreadRun, (void*)t);
- }
- for(int i = 0; i < 5; i++)
- {
- pthread_join(tid[i], nullptr);
- }
- return 0;
- }
运行结果(部分):

这时候我们发现不会出现负数的情况了。

运行结果:


原子性:一行代码经过编译后,只有一行汇编代码。ticket--经过汇编是有多行代码的,但是互斥量在设计的时候,绝对不是 "--" 设计的,而是使用上面的方法完成原子性的逻辑。
实际上只有互斥还是存在一定的问题,比如一个线程申请锁处理完逻辑释放锁之后,它会再次竞争锁,甚至在其他线程还没唤醒之前又进行申请锁释放锁的逻辑。因为其它在挂起等待的线程被唤醒是有一定代价的,竞争力明显小于刚释放锁的线程,除非它的时间片到了或收到信号,这时候被挂起的线程才有可能竞争到锁。

实际上临界区我们加完锁之后,就不用担心重入的问题了。
test.cc
- pthread_mutex_t mymtx = PTHREAD_MUTEX_INITIALIZER;
- class Ticket
- {
- public:
- bool GetTicket()
- {
- bool rets = true;//rets是每个线程私有的
-
- //对临界区进行加锁保护
- //执行这部分代码的执行流是互斥的,也是串行的
- pthread_mutex_lock(&mymtx);
- if(_tickes > 0)
- {
- //抢票
- usleep(1000);
- cout << "我是[" << pthread_self() << "] 我要抢的票是: " << _tickes << endl;
- _tickes--;
- }
- else
- {
- //没有票
- printf("票已经抢空了\n");
- rets = false;
- }
- pthread_mutex_unlock(&mymtx);
-
- return rets;
- }
- private:
- int _tickes = 1000;
- pthread_mutex_t _mtx;
- };
-
- void* ThreadRun(void* args)
- {
- Ticket* t = (Ticket*)args;
-
- while(true)
- {
- if(!t->GetTicket())
- {
- break;
- }
- }
- }
-
- int main()
- {
- Ticket* t = new Ticket;
-
- pthread_t tid[5];
- for(int i = 0; i < 5; i++)
- {
- int* id = new int(i);
- pthread_create(tid+i, nullptr, ThreadRun, (void*)t);
- }
- for(int i = 0; i < 5; i++)
- {
- pthread_join(tid[i], nullptr);
- }
- return 0;
- }

运行结果:
看到这里,给博主点个赞吧~
