关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题
看了互斥锁的简单介绍后,感觉似乎明白了,然后写了以一段程序测试了一下,却遇到了好几个问题,顿时就感觉糊了。在这里记录一下小菜鸟遇到的问题和解决的过程。
首先是下面的代码:
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t number_mutex = PTHREAD_MUTEX_INITIALIZER;
//互斥锁初始化
int globalnumber = 0,temp;
//全局变量
void write_globalnumber(void *arg)
{
printf("write is running\n");
pthread_mutex_lock (&number_mutex);//加锁
globalnumber++;
printf("write_global = %d\n",globalnumber);
sleep(1);
pthread_mutex_unlock(&number_mutex);//解锁
printf("write again\n");
pthread_exit(0);
}
void read_globalnumber(void *arg)
{
printf("read is running\n");
pthread_mutex_lock (&number_mutex);
temp = globalnumber;
pthread_mutex_unlock (&number_mutex);
printf("read_temp = %d\n",temp);
printf("read is end\n");
}
int main()
{
pthread_t thid2,thid1;
printf("start_temp = %d\n",temp);
pthread_create(&thid2,NULL,(void *)read_globalnumber,NULL);//线程2
pthread_create(&thid1,NULL,(void *)write_globalnumber,NULL);//线程1
sleep(5);
printf("globalnumber = %d\ntemp = %d\n",globalnumber,temp);
return 0;
}
反复执行这段程序,发现会出现以下几种不同的运行结果:
(主要是由于线程不定切换引起的)
(1)
(2)
(3)
(4)
子线程的优先级是相同的,主线程创建的两个子线程先执行谁是不确定的,所以运行后第二行可能输出“read is running”,也可能是“write is running”。
但是第二行语句输出后又产生了分歧。图(1)(3)都是先执行了线程2,但是图(3)是直接执行完线程2后才切换到线程1;而图(1)只输出了线程2的一行字符串就切换到了线程1,之后又切换到线程2。
再看图(2)(4)都是先执行了线程1,但是图(1)是执行到sleep时切换到线程2;而图(4)只printf了一行字符串就切换了线程2。
为什么执行的都是同一个线程,而且没有sleep影响,却还会出现不同的执行结果呢?看看两个线程的代码,不论先执行线程1还是线程2,都是遇到pthread_mutex_lock (&number_mutex) 加锁时又出现情况的,有时遇到加锁就会切换到另一个线程了。
我想了想,觉得是这样的:虽然没有遇到sleep休眠,但是线程被分配的时间片时间很短,而加锁应该是个 比较复杂的操作,要记录线程的状态信息等等操作,就需要费点时间,运算时间肯定不是固定的,有时加锁会很快完成,有时可能相对慢了一点。慢了该时间片内就不能执行剩下的语句了,就会切换到另一个线程,于是产生了上面几种不同的运行结果。我目前是这样认为的。
我遇到的主要问题是下面的:
不管先执行哪个进程,上面的几种情况都体现出了互斥锁的作用,我想如果线程没有解锁会是怎样的结果。
我把线程1中的解锁语句注释掉,猜想线程2会无法读到gloabalnumber 的值,所以temp 无法被赋值,输出的是temp 的原始值。确实是这样的,但是却出项了我没有预想到的结果。
先运行未解锁的代码看看:
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t number_mutex = PTHREAD_MUTEX_INITIALIZER;
//互斥锁初始化
int globalnumber = 0,temp;
//全局变量
void write_globalnumber(void *arg)
{
printf("write is running\n");
pthread_mutex_lock (&number_mutex);//加锁
globalnumber++;
printf("write_global = %d\n",globalnumber);
sleep(1);
// pthread_mutex_unlock(&number_mutex); //解锁 注释
printf("write again\n");
pthread_exit(0);
}
void read_globalnumber(void *arg)
{
printf("read is running\n");
pthread_mutex_lock (&number_mutex);
temp = globalnumber;
pthread_mutex_unlock (&number_mutex);
printf("read_temp = %d\n",temp);
printf("read is end\n");
}
int main()
{
pthread_t thid2,thid1;
printf("start_temp = %d\n",temp);
pthread_create(&thid2,NULL,(void *)read_globalnumber,NULL);
pthread_create(&thid1,NULL,(void *)write_globalnumber,NULL);
sleep(5);
printf("globalnumber = %d\ntemp = %d\n",globalnumber,temp);
return 0;
}
结果:
为什么从线程1切换回线程2后,线程2中所后面的2条printf 语句都没有被执行,跳过了。我本以为只是temp 无法被赋值而已,printf还会照常打印的。
又仔细看了看互斥锁的内容,明白了原因:
用 pthread_mutex_lock(&number_mutex) 加锁时,如果number_mutex 已经被锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放。
所以从线程1 的加锁状态切换到线程2时,因为线程1未解锁,当线程2尝试再加锁时就会阻塞,等待线程1先解锁,但是线程1一直到最后终止都没解锁,这就形成了死锁,导致线程2一直处于阻塞状态,没有机会执行剩下的内容,一直到主线程结束,所有线程都关闭。
可以尝试仍不让线程1解锁,再增加一句加锁语句,线程1代码如下:
void write_globalnumber(void *arg)
{
printf("write is running\n");
pthread_mutex_lock (&number_mutex);//加锁
globalnumber++;
pthread_mutex_lock(&number_mutex);//重复加锁
printf("write_global = %d\n",globalnumber);
sleep(1);
// pthread_mutex_unlock(&number_mutex); //解锁 注释掉
printf("write again\n");
pthread_exit(0);
}
结果:
看到线程1第二次加锁之后的语句也都没有没执行。可见未解锁对自身也是有着同样的危害。线程1第二次尝试加锁时,发现已经是加锁状态,所以自身也被阻塞了,但自身被阻塞后就完了,谁也没有能力为他解锁了。锁后面的代码就没有机会执行了。
所以切记在线程退出之前必须解锁或者释放资源,如果形成了死锁,阻塞线程无限的等待下去,造成的后果往往是灾难性的。
下面就说说怎么防止线程在退出时,加锁却没有解锁的状况。用到了下面的一对函数:
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
这两个函数是以宏形式提供的,各含有一半大括号号,所以必须成对使用。
仔细理解这一段说明:
pthread_cleanup_push(), pthread_cleanup_pop() 用于自动释放资源。 从
pthread_cleanup_push()的调用点到 pthread_cleanup_pop()之间的程序段中的终止动作(如调用pthread_exit)都将执行 pthread_cleanup_push()所指定的清理函数。
看下面的代码:
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t number_mutex = PTHREAD_MUTEX_INITIALIZER;
//互斥锁初始化
int globalnumber = 0,temp;
//全局变量
void write_globalnumber(void *arg)
{
printf("write is running\n");
pthread_cleanup_push(pthread_mutex_unlock,&number_mutex);
pthread_mutex_lock (&number_mutex);//加锁
globalnumber++;
printf("write_global = %d\n",globalnumber);
sleep(1);
// pthread_mutex_unlock(&number_mutex);//解锁 注释掉
printf("write again\n");
pthread_exit(0);
pthread_cleanup_pop(0);
}
void read_globalnumber(void *arg)
{
printf("read is running\n");
pthread_mutex_lock (&number_mutex);
temp = globalnumber;
pthread_mutex_unlock (&number_mutex);
printf("read_temp = %d\n",temp);
printf("read is end\n");
}
int main()
{
pthread_t thid2,thid1;
printf("start_temp = %d\n",temp);
pthread_create(&thid2,NULL,(void *)read_globalnumber,NULL);
pthread_create(&thid1,NULL,(void *)write_globalnumber,NULL);
sleep(5);
printf("globalnumber = %d\ntemp = %d\n",globalnumber,temp);
return 0;
}
结果:
看到了这次线程1的解锁语句仍被注释掉,但是线程1和线程2锁后面的printf 如愿被打印出来。这是因为线程1在调用pthread_exit 退出时,执行了pthread_cleanup_push()指定的解锁函数,所以线程1退出后同时释放了资源,不影响线程2对资源的操作。