临界资源:
一个进程的资源对于运行在它内部的线程是共享的,一次只允许一个线程使用的资源叫做临界资源
临界区:
访问临界资源的那段程序叫做临界区
线程的同步:
同步就是协同步调,按照预定的先后顺序执行。
“同”字应是指协同、协助、互相配合。
线程的互斥:
某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。
但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
互斥量(mutex)
多个线程同时访问共享数据时,经常会发生冲突,因此常常引入互斥锁来解决这个问题。
冲突:例如对一个全局变量g_var进行加一操作,这样的操作需要三条指令来完成:(1、把该变量的值从内存读到寄存器;2、寄存器进行加一操作;3、把修改后的值写回内存),因为这三条指令的执行不是原子操作,有可能线程1执行了第一步(把变量值读到寄存器),此时内核调度线程2,线程2也把该值读到寄存器,它们都进行加一操作后写回内存,导致最后结果只加了一次,而我们希望是两次。
解决:引入互斥锁,获得锁的线程可以对变量进行“读-修改-写”操作,这个操作是原子的,完成这个操作后释放锁给别的线程,没有获得锁的线程只能等待而不能访问该共享数据。
有关函数:
1.互斥锁的初始化及销毁
其中初始化既可以使用函数pthread_mutex_init(),也可以定义pthread_mutex_t类型的变量mutex,并赋值PTHREAD_MUTEX_INITIALIZER
2.对mutex的加锁及解锁
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
Mutex的lock和unlock的伪码(以x86的xchg指令为例):
不使用互斥量代码:
#include <stdio.h> #include <pthread.h> void *thread(void *arg) { int count=2000; while(count-- >0){ int var=g_var; printf("this is thread%d,g_var=%d\n",(int)arg,g_var); g_var=var+1; } } int main() { pthread_t tid1,tid2; if(pthread_create(&tid1,NULL,thread,(void*)1)!=0){ printf("creat tid1 is failed\n"); } if(pthread_create(&tid1,NULL,thread,(void*)2)!=0){ printf("creat tid1 is failed\n"); } pthread_join(tid1,NULL); pthread_join(tid2,NULL); return 0; }
运行结果:
第一次运行线程1和线程2一共加了2275次
第二次运行一共把g_var加了2000次
使用互斥量实现代码:
include <stdio.h> #include <pthread.h> //pthread_mutex_t mutex_lock=PTHREAD_MUTEX_INITIALIZER; struct info { int _var; pthread_mutex_t *_mutex_lock; }info; int g_var=0; void *thread(void *arg) { struct info* _lock=(struct info*)arg; int count=2000; while(count-- >0){ pthread_mutex_lock(_lock->_mutex_lock); int var=g_var; printf("this is thread%d,g_var=%d\n",_lock->_var,g_var); g_var=var+1; pthread_mutex_unlock(_lock->_mutex_lock); } } int main() { pthread_t tid1,tid2; pthread_mutex_t mutex_lock; int err=pthread_mutex_init(&mutex_lock,NULL); if(err!=0){ printf("%s\n",strerror(err)); } struct info info1; info1._var=1; info1._mutex_lock=&mutex_lock; if(pthread_create(&tid1,NULL,thread,&info1)!=0){ printf("creat tid1 is failed\n"); } struct info info2; info2._var=2; info2._mutex_lock=&mutex_lock; if(pthread_create(&tid2,NULL,thread,&info2)!=0){ printf("creat tid2 is failed\n"); } pthread_join(tid1,NULL); pthread_join(tid2,NULL); err=pthread_mutex_destroy(&mutex_lock); if(err!=0){ printf("%s\n",strerror(err)); } return 0; }
运行结果:
线程1和线程2共执行了4000次
总结:当一个线程已经获得锁,另一个线程需挂起等待,当该线程释放锁后,会唤醒正在等待线程。每个mutex都有一个等待队列,当线程挂起时,就加入该等待队列,状态设置为睡眠。一个线程要唤醒别的线程,只需要从等待队列中取出一项加入就绪队列并把它的状态设置为就绪。
有的时候一个线程会两次获得锁,第一次正常获得,第二次获得所得时候发现锁已经被别占用,它就会挂起,导致自己占用了锁却处于挂起状态,这样就产生了死锁。
死锁:
产生死锁的原因
1、进程资源不足
2、进程推进的顺序不合适
3、系统资源的分配不当
产生死锁的必要条件:
1、互斥条件:一个资源一次只能被一个进程使用
2、请求与保持条件:一个已经进程因请求资源而阻塞,对已获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在没使用完之前,不能强行剥夺
4、循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系
产生死锁时,以上四个必要条件一定都成立。