线程学习三:互斥量的使用

线程同步

解决竞争问题就要用到线程同步,用到互斥量pthread_mutex_t这个机制。
互斥量:相当于有一把琐,当一个线程抢到了这把锁,就把门锁上,然后再操作。出来再解锁,再由其他的线程抢锁。
是限制一段代码,以独占的形式实现.

相关函数:
pthread_mutex_destroy();
pthread_mutex_init();
— destroy and initialize a mutex

#include <pthread.h>
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);//动态初始化
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化方式,用宏来初始化

如果互斥量是一个变量的话,就用静态比较方便,用的就是默认的属性。
如果更改互斥量的属性或是互斥量位于结构体当中,就要用动态的初始化。

pthread_mutex_lock();
pthread_mutex_trylock();
pthread_mutex_unlock();
— lock and unlock a mutex

    #include <pthread.h>
           int pthread_mutex_lock(pthread_mutex_t *mutex);//阻塞
           int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞
           int pthread_mutex_unlock(pthread_mutex_t *mutex);

lock和 unlock之间的地方我们通常叫做临界区

互斥量的应用

在下面一段代码中,20个线程,若是一个线程在读的时候,别的线程在写,就会出现错误。所以20个线程不可以同时读,也不可以同时写。关闭文件也必须一个一个做。
同一时候只有一个线程来做的代码叫做临界区,我们需要在临界区前lock住,退出临界区的时候进行unlock。
互斥量限制一段代码的使用。

如下代码,加上临界区的使用。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THRNUM 20
#define FNAME "/tmp/out"
#define LINESIZE 1024
static pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER;
static void * thr_add(void *p)
{
        FILE *fp;
        char linebuf[LINESIZE];
        fp=fopen(FNAME,"r+");
        if(fp==NULL)
        {
        perror("fopen()");
        exit(1);
        }
        pthread_mutex_lock(&mut);
        fgets(linebuf,LINESIZE,fp);
        fseek(fp,0,SEEK_SET);
        fprintf(fp,"%d\n",atoi(linebuf)+1);

        fclose(fp);
        pthread_mutex_unlock(&mut)
        pthread_exit(NULL);
}

int main()
{
        pthread_t tid[THRNUM];
        int i,err;
        for(i=0;i<THRNUM;i++)
        {
                err=pthread_create(tid+i,NULL,thr_add,NULL);
                if(err)
                {
                        fprintf(stderr,"pthread_create():%s\n",strerror(err));
                 }
           }

        for(i=0;i<THRNUM;i++)
                pthread_join(tid[i],NULL);
        exit(0);

}

运行结果为21是正确的

在来个小例子,有四个线程,向终端不停的输出abcd
第一个线程打印a,第二个线程打印b······

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THRNUM 4

static void *thr_func(void *p)
{
        int c='a'+(int)p;

        while(1)
           write(1,&c,1);
        pthread_exit(NULL);

}

int main()
{
        int i,err;
        pthread_t tid[THRNUM];
        for(i=0;i<THRNUM;i++)
        {
                err=pthread_create(tid+i,NULL,thr_func,(void *)i);
                if(err)
                {
                fprintf(stderr,"pthread_create():%s\n",strerror(err));
                exit(1);
                }
        }

        alarm(5);
        for(i=0;i<THRNUM;i++)
        {
                pthread_join(tid[i],NULL);
        }
        exit(0);

}

alarm(5):当前进程会在五秒钟之后被杀掉。
从结果可以看出没有很强的规律性,乱序输出的。

如果要规律性的输出abcd,abcd······,思路:打印完a,解锁b,打印完b,解锁c,打印完c,解锁d。需要四个1互斥量

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THRNUM 4

static pthread_mutex_t mut[THRNUM];
static int next(int n)
{
        if(n+1==THRNUM)
                return 0;
        return n+1;

}

static void *thr_func(void *p)
{
        int n=(int)p;
        int c='a'+n;

        while(1)
        {
        pthread_mutex_lock(mut+n);

        write(1,&c,1);

        pthread_mutex_unlock(mut+next(n));
        }
        pthread_exit(NULL);

}
int main()
{
        int i,err;
        pthread_t tid[THRNUM];
        for(i=0;i<THRNUM;i++)
        {
                pthread_mutex_init(mut+i,NULL);
                pthread_mutex_lock(mut+i);
                err=pthread_create(tid+i,NULL,thr_func,(void *)i);
                if(err)
                {
                fprintf(stderr,"pthread_create():%s\n",strerror(err));
                exit(1);
                }

        }
        pthread_mutex_unlock(mut+0);
        alarm(5);

        for(i=0;i<THRNUM;i++)
        {
                pthread_join(tid[i],NULL);
        }

        exit(0);

注意,加锁或是解锁,针对的是一段代码,而不是变量。
运行结果:
在这里插入图片描述

线程池的实现:

筛质数
之前是201个线程对应201个tid
现在做出改变201个线程对应N个
(相当于一个任务池),不是正规的线程池

main向下仍任务,例如把3000000扔到num中,下面的线程开始抢任务,把这个数拿走,然后main再把3000001仍进num,闲着的两个线程就抢这个3000001。
在这里插入图片描述

这里用num>0表示有一个待计算的任务;num=0时表示,没有任务;num=-1时表示退出。
以上功能就需要拿到任务的线程将num变为0,表明当前任务已经被抢走。
而main在知道num为0后,下发下一个任务。
当main下发完最后一个任务时,任务被拿走后,main将num变为-1,提醒下游线程退出,上游的线程就等着收尸。是一种能者多劳的一种实现。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define LEFT  30000000
#define RIGHT 30000200
#define THRNUM 4
static void * thr_prime(void *p);
static int num=0;
static pthread_mutex_t mut_num=PTHREAD_MUTEX_INITIALIZER;

int main()
{
        int i,err;
        pthread_t tid[THRNUM];
        for(i=0;i<=THRNUM;i++)
        {
            
                err=pthread_create(tid+i,NULL,thr_prime,(void *)i);
                if(err)
                {
                        fprintf(stderr,"pthread_create():%s\n",strerror(err));
                        exit(1);
                }

        }


    for(i=LEFT;i<=RIGHT;i++)
    {
    pthread_mutex_lock(&mut_num);
        while(num!=0)
        {pthread_mutex_unlock(&mut_num);
            sched_yield();
        pthread_mutex_lock(&mut_num);
        
        }
    num=i;
    pthread_mutex_unlock(&mut_num);

    }

    pthread_mutex_lock(&mut_num);
    while(num!=0)
    {
        pthread_mutex_unlock(&mut_num);
        sched_yield();
        pthread_mutex_lock(&mut_num);
    }

    num=-1;
    pthread_mutex_unlock(&mut_num);


        for(i=0;i<=THRNUM;i++)
        {
                pthread_join(tid[i],NULL);
        }
    pthread_mutex_destroy(&mut_num);
        exit(0);
}

static void * thr_prime(void *p)
{
        int i,j,mark;
    while(1)
    {
    pthread_mutex_lock(&mut_num);
    while(num==0)
    {
        pthread_mutex_unlock(&mut_num);
        sched_yield();
        pthread_mutex_lock(&mut_num);
    }
    if(num==-1)
        {
            pthread_mutex_unlock(&mut_num);
            break;
        }
        i=num;
    num=0;
    pthread_mutex_unlock(&mut_num);
        mark=1;
        for(j=2;j<i/2;j++)
        {
                if(i%j==0)
                {
                mark=0;
                break;
                }
        }

        if(mark)
        printf("[%d]%d is a primer\n",(int)p,i);
    }
        pthread_exit(NULL);
}

sched_yied():出让调度器给别的进程,不会造成当前进程的调度颠簸,可以看成一种很短的sleep()
运行结果:
在这里插入图片描述


版权声明:本文为m0_52547536原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。