本章主要讲解学习Linux中对多线程的执行中的同步与互斥
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int thickets=100;//100张票
//thickets--表示抢票
void* Routine(void* arg)
{
while(1)
{
if(thickets>0)
{
usleep(30000);//抢票时间
printf("%s get a thickets, now thickets' number:%d\n",(char*)arg,--thickets);
}
else
break;
}
return NULL;
}
int main()
{
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,Routine,(void*)"thread 1");
pthread_create(&tid2,NULL,Routine,(void*)"thread 2");
pthread_create(&tid3,NULL,Routine,(void*)"thread 3");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
return 0;
}
注:变量tickets被多个执行流同时访问,所以thickets就是一个临界资源,当访问临界资源时,判断tickets是否大于0、打印剩余票数以及
--tickets
的代码也就是临界区
if语句判断条件为真以后,代码可以并发的切换到其他线程 usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段 –ticket操作本身就不是一个原子操作,可能在执行当中也被切换成其他线程
当thickets为1时,一个线程进行if判断为真,进入代码段,当执行到usleep进行系统调用休眠,返回时到用户态时线程发生切换,多个线程此时也进行if判断为真(thickets还是1),这些线程当进行打印的时候进行了多次的减减操作,也就造成了负数的情况
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
注:因为减减操作并不是原子的,当减减操作第一步执行完(thickets=100),可能该线程的时间片到了(寄存器中的数据被保存eax=100),其他线程切入,而切入的线程执行了多次减减并写会到内存(thickets=80),当切出的线程切回时,恢复线程上下文数据(eax=100),再进行减减(eax=99),把数据写回到内存时(thickets=99),此时的数据的值只达到了一次减减的效果,此时的资源并不安全
注:要做到这三点,本质上就是需要一把锁,Linux上提供的这把锁叫互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);
参数:mutex:要初始化的互斥量;attr:互斥量的属性,一般设置为NULL
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int thickets=100;//100张票
//thickets--表示抢票
pthread_mutex_t lock;//线程共用一个互斥锁
void* Routine(void* arg)
{
while(1)
{
pthread_mutex_lock(&lock);
if(thickets>0)
{
usleep(100000);//抢票时间
printf("%s get a thickets, now thickets' number:%d\n",(char*)arg,--thickets);
pthread_mutex_unlock(&lock);
}
else
{
pthread_mutex_unlock(&lock);
break;
}
usleep(100000);
}
return NULL;
}
int main()
{
pthread_mutex_init(&lock,NULL);
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,Routine,(void*)"thread 1");
pthread_create(&tid2,NULL,Routine,(void*)"thread 2");
pthread_create(&tid3,NULL,Routine,(void*)"thread 3");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_mutex_destroy(&lock);
return 0;
}
注:在交换和赋值的过程中本质就是让竞争的多线程中保证中有一个线程的交换得到的寄存器数据为1,即保证同一时刻只有一个竞争的线程为1,由此才能往下执行,否则只能进行等待
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态
注:对于死锁,四个条件缺一不可
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);
int pthread_cond_destroy(pthread_cond_t *cond)
PTHREAD_COND_INITIALIZER
初始化的条件变量不需要销毁
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
void* Routine1(void* arg)
{
//被调度线程执行
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("%s is running...\n",(char*)arg);
}
}
void* Routine2(void* arg)
{
int cnt=0;
while(1)
{
if(cnt%3!=0)
pthread_cond_signal(&cond);
else
pthread_cond_broadcast(&cond);
cnt++;
sleep(1);
}
}
int main()
{
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,Routine1,(void*)"thread 1");
pthread_create(&tid2,NULL,Routine1,(void*)"thread 2");
pthread_create(&tid3,NULL,Routine1,(void*)"thread 3");
pthread_create(&tid4,NULL,Routine2,(void*)"thread 4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
注:如果不释放互斥锁,那么其他线程无法成功申请到锁进而改变数据,也就没有办法通知等待的线程,那么申请到锁的线程一直等待,别的线程无法获取锁也无法通知,也就会造成死锁
pthread_mutex_lock(&mutex);
while (condition_is_false)
{
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
while (条件为假){
pthread_cond_wait(cond, mutex);
}
//修改条件
pthread_mutex_unlock(&mutex);
注:这里可能存在被伪唤醒的情况,当唤醒的时候可能竞争锁失败,继续等待,其他线程竞争成功执行后并释放锁,此时条件判断为假,但是该线程竞争到锁后会继续往下执行,如果没有再次进行判断可能造成错误,使用while循环判断保证醒来后条件一定为真才往下走
pthread_mutex_lock(&mutex);
//设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
参数:sem:需要销毁的信号量 返回值:销毁信号量成功返回0,失败返回-1
int sem_wait(sem_t *sem); //P()
int sem_post(sem_t *sem);//V()
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
class Sem{
public:
Sem(int num)
{
sem_init(&_sem, 0, num);
}
~Sem()
{
sem_destroy(&_sem);
}
void P()
{
sem_wait(&_sem);
}
void V()
{
sem_post(&_sem);
}
private:
sem_t _sem;
};
Sem sem(1); //二元信号量
int tickets = 2000;
void* TicketGrabbing(void* arg)
{
std::string name = (char*)arg;
while (true){
sem.P();
if (tickets > 0){
//usleep(3000);
std::cout << name << " get a ticket, tickets left: " << --tickets << std::endl;
sem.V();
usleep(3000);
}
else{
sem.V();
break;
}
}
std::cout << name << " quit..." << std::endl;
pthread_exit((void*)0);
}
int main()
{
pthread_t tid1, tid2, tid3, tid4;
pthread_create(&tid1, nullptr, TicketGrabbing, (void*)"thread 1");
pthread_create(&tid2, nullptr, TicketGrabbing, (void*)"thread 2");
pthread_create(&tid3, nullptr, TicketGrabbing, (void*)"thread 3");
pthread_create(&tid4, nullptr, TicketGrabbing, (void*)"thread 4");
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
pthread_join(tid4, nullptr);
return 0;
}