关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题


        看了互斥锁的简单介绍后,感觉似乎明白了,然后写了以一段程序测试了一下,却遇到了好几个问题,顿时就感觉糊了。在这里记录一下小菜鸟遇到的问题和解决的过程。

     

      首先是下面的代码:

#include
#include

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)

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第1张图片

(2)

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第2张图片

(3)

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第3张图片

(4)

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第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
#include

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;
}

结果:


关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第5张图片

       为什么从线程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);
}

结果:

关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第6张图片

       看到线程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
#include

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;
}

结果:

   关于线程加了锁未解锁和pthread_cleanup_push函数遇到的问题_第7张图片
       看到了这次线程1的解锁语句仍被注释掉,但是线程1和线程2锁后面的printf 如愿被打印出来。这是因为线程1在调用pthread_exit 退出时,执行了pthread_cleanup_push()指定的解锁函数,所以线程1退出后同时释放了资源,不影响线程2对资源的操作。

       

你可能感兴趣的:(学习记录)