Linux多线程编程(一):http://blog.csdn.net/llzk_/article/details/55670172
由上篇博客我们可以知道,多个线程是共享同一份内存的。这就意味着这么多线程共享同一份数据资源。这时就很可能出现多个线程因竞争同一份资源而发生冲突的问题。就算从程序不挂掉,运行结果也不会太正确。
例如,现在有两个线程A,B分别要对一个数据a = 5执行加1操作。正常来说,a最后的值应该为7。
加1操作共分下面三步:
由于加1操作分三步进行, 并不是原子操作(要么不做,要么一次性完成)。所以这就为错误的出现提供了无限的可能。如下图:
当线程A正在执行第二步的时候,线程B切入,从内存读到了a的值为5。并执行了加1操作,最后内存中a的值是6,并不是我们想要的7。这与线程B切入的时间有关。
要想的到正确的结果,我们不得不为这些线程加一些限制,就是我们所谓的同步与互斥。
一份资源在一个时刻内只能被一个进程或线程访问。
比如现在有一个门。门外墙上只挂着一把钥匙。一次只有一个人可以拿着钥匙打开门进去。钥匙也一并被拿进去。此时门外的人只能等待。门内的资源在这段等待时间内只被门内的人一人占用。这就是所谓的互斥。
进程按一定的顺序访问临界资源,同步强调的是协同,一般都在互斥的前提下,但在有些场景下也不需要互斥。
门外的人按照一定的规则按顺序拿钥匙进门就是同步。
互斥量,也叫互斥锁。是我们实现同步重要工具。在线程访问共享资源前对互斥量进行设置(加锁),在线程访问共享资源结束后释放互斥量(解锁)。
通过加锁解锁,我们可以将原来的分多步的操作变成一个“原子”操作。系统只会在执行完“锁内”的代码后,才可能会被切去执行其他线程。这个“锁”,也可以理解为上文所描述的“钥匙”。在一个线程占用锁资源时(钥匙),任何其他再试图申请同一份锁资源的线程,都会被阻塞。直到当前线程释放该锁资源。正规来说,对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程释放该互斥锁。
在互斥锁被释放时,所有因为该锁被阻塞的线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁(获取钥匙),其他线程就会看到锁资源依然被占用,只能回去再次等待。
在这种方式下,我们可以达到一个时刻只有一个线程可以向前执行,并且多线程时会按照一定的顺序进行协同。
这种互斥锁也可以理解为二元信号量。
①调用函数pthread_mutex_init。
int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)
参数mutex为在外面定义的一个pthread_mutex_t数据类型的数据指 针,init函数调用完成后,互斥锁的值会放在mutex指向的内存单元。要用默认的属性初始化互斥量,就将参数2attr设为NULL
②直接定义一个互斥锁,用下面宏定义给它赋值。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
此方式相当于方式①的attr设为NULL。即默认的属性初始化互斥量。
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
为了实现互斥锁的操作,大多数体系结构都提供了swap或者exchange指令,该指令的作用是吧寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,及时多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令也只能等待总线周期。
在上面说过,当一个线程已经占用锁资源时,其他线程就会因为得不到锁资源而被阻塞。如果一个线程不想被阻塞的话,可以使用pthread_mutex_trylock尝试对互斥量进行加锁,如果调用时互斥量处于未锁住状态,那么pthread_mutex_trylock将会锁住该互斥量,不会出现阻塞直接返回0。否则就会trylock失败,返回EBUSY。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex\_destroy(pthread_mutex_t *mutex);
补充:
当我们使用锁时,就要避免死锁的现象发生。什么叫死锁?
比如:(还是上面门的例子)A拿到了钥匙,可他忘记他拿到了,所以他就跟其他人一样继续等待钥匙(锁资源)。这时就出现了死锁。
常见死锁出现的原因:
死锁产生的必要条件:
避免死锁:破坏任意一个死锁产生的必要条件。
现有两个线程,分别对一个全局变量做5000次加1操作,为了可以明显观察到到线程切换带来的错误,我们在加1操作中间加上一句printf,printf会调用系统调用write。正确结果应为10000。试一下下列代码的结果:
#include
#include
int count = 0;
void* thread_run(void* arg)
{
int i = 0;
int num = 5000;
int val = 0;
while(i < num)
{
val = count;
printf("val : %d\n",val);
count = val + 1;
i++;
}
return NULL;
}
int main()
{
printf("thread\n");
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1,NULL,thread_run,NULL);
pthread_create(&tid2,NULL,thread_run,NULL);
//pthread_create()
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("count: %d\n",count);
return 0;
}
再验证下列代码,加锁之后的代码:
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;
void* thread_run(void* arg)
{
int i = 0;
int num = 5000;
int val = 0;
while(i < num)
{
pthread_mutex_lock(&mutex);
val = count;
printf("val : %d\n",val);
count = val + 1;
i++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
printf("thread\n");
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1,NULL,thread_run,NULL);
pthread_create(&tid2,NULL,thread_run,NULL);
//pthread_create()
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&mutex);
printf("count: %d\n",count);
return 0;
}