线程同步

来源:互联网 发布:文字转语音软件 编辑:程序博客网 时间:2024/06/01 10:31

1.线程为什么要同步?

一个简单的问题如下代码

其实直接tmp++打印也可以只不过要特别多的数据才会出现错误

#include <unistd.h>#include <pthread.h>#include <iostream>using namespace std;int tmp;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;void *thread(void *arg){//cout<<"thread id is"<<pthread_self()<<endl;//pthread_mutex_lock(&mutex);int i=1000;while(i--){int get=tmp+1;tmp=get;cout<<tmp<<endl;}//cout<<"Now a is"<<tmp<<endl;//pthread_mutex_unlock(&mutex);cout<<tmp<<endl;return NULL;;}int main(){pthread_t id,id1;cout<<"main thread id is"<<pthread_self()<<endl;cout<<"tmp is"<<tmp<<endl;if(!pthread_create(&id,NULL,thread,NULL)){pthread_create(&id1,NULL,thread,NULL);cout<<"pthread is success"<<endl;}else{cout<<"pthread is failed"<<endl;}pthread_join(id,NULL);pthread_join(id1,NULL);pthread_mutex_destroy(&mutex);return 0;}
算出的结果每次都不一样我们想要的是2000产生原因线程切换出去和切换回来认为tmp的值没有变过所以会出现这种情况;因为为赋值和++不是原子操作;很容易出现共享内存访问冲突的问题;

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。

linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。

多线程相当于一个并发(concunrrency)系统。并发系统一般同时执行多个任务。如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题。比如说,我们有一个多线程火车售票系统,用全局变量i存储剩余的票数。多个线程不断地卖票(i = i - 1),直到剩余票数为0。所以每个都需要执行如下操作:

对于多线程程序来说,同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源 。而在此时间内,不允许其它的线程访问该资源。我们可以通过互斥锁(mutex)条件变量(condition variable)读写锁(reader-writer lock)来同步资源

互斥锁:保证操作的原子性不被 打断

互斥锁是一个特殊的变量,它有锁上(lock)和打开(unlock)两个状态。互斥锁一般被设置成全局变量。打开的互斥锁可以由某个线程获得。一旦获得,这个互斥锁会锁上,此后只有该线程有权打开。其它想要获得互斥锁的线程,会等待直到互斥锁再次打开的时候。我们可以将互斥锁想像成为一个只能容纳一个人的洗手间,当某个人进入洗手间的时候,可以从里面将洗手间锁上。其它人只能在互斥锁外面等待那个人出来,才能进去。在外面等候的人并没有排队,谁先看到洗手间空了,就可以首先冲进去

互斥锁
  通过锁的机制实现线程间的互斥,同一时刻只有一个线程可以锁定它,当一个锁被某个线程锁定的时候,如果有另外一个线程尝试锁定这个临界区(互斥体),则第二个线程会被阻塞,或者说被置于等待状态。只有当第一个线程释放了对临界区的锁定,第二个线程才能从阻塞状态恢复运行。


  int pthread_mutex_init(pthread_mutex_t* mutex, const thread_mutexattr_t* mutexattr);初始化一个互斥锁。


  int pthread_mutex_lock(pthread_mutex_t* mutex);如果mutex被锁定,当前进程处于等待状态;否则,本进程获得互斥锁并进入临界区。


  int pthread_mutex_trylock(pthread_mutex_t* mutex);和lock不同的时候,尝试获得互斥锁不成功不会使得进程进入阻塞状态,而是继续返回线程执行。该函数可以有效避免循环等待锁,如果trylock失败可以释放已经占有的资源,这样可以避免死锁。


  int pthread_mutex_unlock(pthread_mutex_t* mutex);释放互斥锁,并使得被阻塞的线程获得互斥锁并执行。


  int pthread_mutex_destroy(pthread_mutex_t* mutex);用来撤销互斥锁的资源。


复制代码
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);




void pthread1(void* arg){
   pthread_mutex_lock(&mutex);
   .....//临界区
   pthread_mutex_unlock(&mutex);
}




void pthread2(void* arg){
   pthread_mutex_lock(&mutex);
   .....//临界区
   pthread_mutex_unlock(&mutex);
}
复制代码

死锁出现的两种情况

  1. A同一个线程在拥有A锁的情况下再次请求获得A

    B线程一拥有A锁,请求获得B锁;线程二拥有B锁,请求获得A锁,出现死锁,最终导致的结果是互相等待。

谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。

虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0P1P2···Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……Pn正在等待已被P0占用的资源。

在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解除死锁。目前处理死锁的方法可归结为以下四种:

1) 预防死锁。

  这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。

2) 避免死锁。

  该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。

3)检测死锁。

  这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。

4)解除死锁。

  这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。

列举说明linux系统的各类异步机制

读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。适用于读的次数大于写的次数的数据结构。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
读锁锁住,加读锁,可以;加写锁会被阻塞,但此时会阻塞后续的读锁请求,防止读锁长期占用无法进入写模式。写锁就是互斥锁。

int pthread_rwlock_init(pthread_rwlock_t* rwlock, const pthread_rwlockattr_t* attr);初始化读写锁
int pthread_destroy(pthread_rwlock_t* rwlock);销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);加写锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);解锁
条件变量
  信号量只有锁住和不锁两种状态,而且当条件变量和信号量一起使用时,允许线程以无竞争的方式等待特定的条件发生。


  条件本身是由互斥量保护的:线程在改变条件状态之前必须先锁住互斥量。


  int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* attr);初始化动态分配的条件变量;也可以直接用PTHREAD_INITIALIZER直接赋值给静态的条件变量


  int pthread_cond_destroy(pthread_cond_t* cond)撤销条件变量资源;


  int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);使用该函数使得等待条件变量为真。线程被条件变量cond阻塞。


  int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex,const struct timespec* tspr);与wait类似,只是经历tspr时间后,即使条件变量不满足,阻塞也被解除,返回错误码。


  int pthread_cond_signal(pthread_cond_t* cond);唤醒因为条件变量阻塞的线程。


  int pthread_cond_broadcast(pthread_cond_t* cond);唤醒等待该条件的所有线程。


  


复制代码
pthread_cond_t cond;
pthread_mutex_t mutex;
int count=0;
void pthread1(void* arg){
    pthread_mutex_lock(&mutex);
    while(count==0)
        pthread_cond_wait(&cond,&mutex);
    count--;
    pthread_mutex_unlock(&mutex);
}
void pthread2(void* arg){
    pthread_mutex_lock(&mutex);
    if(count==0)
        pthread_cond_signal(&cond);
    count++;
    pthread_mutex_unlock(&mutex);
}
复制代码
自旋锁
  互斥量阻塞线程的方式是使其进入睡眠,而自旋锁是让线程忙等,即不会使其睡眠,而是不断循判断自旋锁已经被解锁。


  适用于占用自旋锁时间比较短的情况。


信号量
介绍一下POSIX(POSIX标准定义了操作系统应该为应用程序提供的接口标准,换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。)的信号量机制,定义在头文件/usr/include/semaphore.h


1)初始化一个信号量:sem_init()


int sem_init(sem_t* sem,int pshared,unsigned int value);


pshared为0时表示该信号量只能在当前进程的线程间共享,否则可以进程间共享,value给出了信号量的初始值。


2)阻塞线程


sem_wait(sem_t* sem)直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少;sem_trywait(sem_t* sem)是wait的非阻塞版本,它直接将sem的值减一,相当于P操作。


3)增加信号量的值,唤醒线程


sem_post(sem_t* sem)会使已经被阻塞的线程其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。相当于V操作。


3)释放信号量资源


sem_destroy(sem_t* sem)用来释放信号量sem所占有的资源


原创粉丝点击