linux高级编程笔记(八)——线程同步

目录

一、互斥量

1 临界区

2 互斥量实例

3 死锁

4 读写锁(读共享,写独占)

二 、条件变量

1 pthread_cond_wait函数

2 pthread_cond_timedwait函数

3 生产者与消费者模型

三、信号量

1 sem_init函数

2 线程同步

四、进程间同步

1 使用互斥量进行同步

2 使用文件锁进行同步


一、互斥量

mutex操作原语

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);//动态初始化
int pthread_mutex_lock(pthread_mutex_t *mutex);//解锁。mutex--
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);// 加锁。mutex++

1 临界区

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

临界区的选定因尽可能小,如果选定太大会影响程序的并行处理性能。

2 互斥量实例

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

#define NLOOP 5000

int counter; /* incremented by threads */

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit(void *vptr)
{
    int i, val;
    for (i = 0; i < NLOOP; i++) {
        pthread_mutex_lock(&counter_mutex);
        val = counter;
        printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
        counter = val + 1;
        pthread_mutex_unlock(&counter_mutex);
    }
    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t tidA, tidB;
    pthread_create(&tidA, NULL, doit, NULL);
    pthread_create(&tidB, NULL, doit, NULL);
    /* wait for both threads to terminate */
    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);
    return 0;
}

3 死锁

  • 同一个线程在拥有A锁的情况下再次请求获得A锁
  • 线程一拥有A锁,请求获得B锁;线程二拥有B锁,请求获得A锁死锁

代码示例:

4 读写锁(读共享,写独占)

一把锁具备三种状态

1.读模式下加锁状态(读锁)

2.写模式下加锁状态(写锁)

3.不加锁

 

如上图所示,如果有T1~T4四个线程,按顺序分别以读、读、写、读的方式获取读写锁来操作资源,那么 只有T1、T2这两个线程成功拿到锁。根据读写锁的特性,由于读的时候是共享,因此T1和T2都可以拿到读写锁来读取资源,当T3线程想要获取读写锁来写数据时,由于写独占,而此时的锁被T1和T2拿到,因此T3获取读写锁会失败。需要注意的是,T4尽管也是读取资源,由于写的优先级大于读,在T3未拿到锁的时候,T4只能继续等待。

  1. 读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。
  2. 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  3. 读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。读写锁非常适合于对数据结构读的次数远大于写的情况。

主要应用函数:

pthread_rwlock_t
pthread_rwlock_init
pthread_rwlock_destroy
pthread_rwlock_rdlock //读模式加锁
pthread_rwlock_wrlock//写模式加锁
pthread_rwlock_tryrdlock
pthread_rwlock_trywrlock
pthread_rwlock_unlock//解锁

代码示例:

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

pthread_rwlock_t rwlock;
int counter;

void *pthread_write(void *arg)
{
    int i=(int)arg;
    int t;
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        usleep(1000);
        printf("--------#%dth write:%lu, counter=%d, -------\n",i,pthread_self(),counter++);
        pthread_rwlock_unlock(&rwlock);
        usleep(10000);
    }
    return NULL;
}

void *pthread_read(void *arg)
{
    int i=(int)arg;
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("--------#%dth read:%lu, counter=%d, -------\n",i,pthread_self(),counter);
        pthread_rwlock_unlock(&rwlock);
        usleep(2000);

    }
    return NULL;
}

int main()
{
    int i;

    pthread_t pt[10];
    pthread_rwlock_init(&rwlock,NULL);

    counter=0;
    for(i=0;i<5;i++)
    {
        pthread_create(&pt[i],NULL,pthread_write,(void *)i);
    }

    for(i=5;i<10;i++)
    {
        pthread_create(&pt[i],NULL,pthread_read,(void *)i);
    }

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

    return 0;
}

二 、条件变量

 条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

主要使用函数:

pthread_cond_init
pthread_cond_destroy
pthread_cond_wait //等待条件满足
pthread_cond_timewait
pthread_cond_signal//唤醒等待条件的至少一个线程
pthread_rwlock_broadcastl//唤醒等待条件的所有线程

1 pthread_cond_wait函数

描述:

1.阻塞等待条件变量cond(参1)满足

2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);

1.2.两步为一个原子操作。类似时序竞态中的sigsuspend函数

3.当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

2 pthread_cond_timedwait函数

 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

描述:

线程等待一定的时间,如果超时或有条件触发,线程唤醒。

参数:

cond

mutex

abstime 从1970年1月1日00:00:01秒到当前这一刻的秒数,结构体定义如下所示。

struct timeval{

time_t tv_sec; //秒

long tv_nsec; //纳秒

}

3 生产者与消费者模型

代码示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>//malloc
#include <unistd.h>//sleep


struct msg{
        int num;
        struct msg *next;
};

struct msg* head=NULL;


pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void * consumer(void *p)
{
        struct msg * mp;

        for(;;)
        {
                pthread_mutex_lock(&lock);
                while(head==NULL)
                {
                        pthread_cond_wait(&has_product,&lock);
                }

                //取走一个资源
                mp = head;      
                head = head->next;
                printf("Consumer %lu take '%d'\n",pthread_self(),mp->num);

                pthread_mutex_unlock(&lock);

                free(mp);
                sleep(rand()%4);
        }
}

void * producer(void *p)
{
        struct msg * mp;
        int i=0;
        for(;;)
        {
                mp = (struct msg*) malloc(sizeof(struct msg));
                mp->num = i++;
                pthread_mutex_lock(&lock);
                mp->next = head;
                head = mp;

                printf("Producer %lu produce '%d'\n",pthread_self(),mp->num);

                pthread_mutex_unlock(&lock);
                pthread_cond_broadcast(&has_product);

                sleep(rand()%4);
        }

}

int main()
{
        pthread_t th_consumer,th_producer;
        pthread_t th_consumer1,th_producer2;

        pthread_create(&th_consumer,NULL,consumer,NULL);
        pthread_create(&th_consumer1,NULL,consumer,NULL);
        pthread_create(&th_producer,NULL,producer,NULL);
//      pthread_create(&th_producer2,NULL,producer,NULL);

        pthread_join(th_consumer,NULL);
        pthread_join(th_producer,NULL);


        return 0;
}

三、信号量

sem_init 
sem_destroy
sem_wait //1.信号量大于0,信号量--。2.信号量等于0,造成阻塞
sem_trywait
sem_timedwait//
sem_post//

1 sem_init函数

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

sem 信号量

pshared  shared表示进程间共有;private表示线程间共有 

2 线程同步

代码示例:

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

#define NUM 5

int queue[NUM];
sem_t blank_number,product_number;

void * producer(void * arg)
{
        int i =0;

        while(1)
        {
                sem_wait(&blank_number);
                queue[i] = rand()%1000+1;
                printf("------produce---%d\n",queue[i]);
                sem_post(&product_number);

                i=(i+1)%NUM;
                sleep(rand()%1);
        }
}

void * consumer(void * arg)
{
        int i =0;

        while(1)
        {
                sem_wait(&product_number);
                printf("-----consumer---%d\n",queue[i]);
                queue[i]=0;
                sem_post(&blank_number);
                i=(i+1)%NUM;
                sleep(rand()%1);
        }

}

int main()
{
        pthread_t pid,cid;

        sem_init(&blank_number,0,NUM);
        sem_init(&product_number,0,0);

        pthread_create(&pid,NULL,producer,NULL);
        pthread_create(&cid,NULL,consumer,NULL);
        pthread_join(pid,NULL);
        pthread_join(cid,NULL);

        sem_destroy(&blank_number);
        sem_destroy(&product_number);

        return 0;
}

四、进程间同步

1 使用互斥量进行同步

#include <pthread.h>
#include <sys/mmap.h>
#include <sys/wait.h>

int main()
{
        int fd,i;

        struct mt * mm;
        pid_t pid;

        fd = open("mt_test",_CREAT|ORDWR,0777);
        ftruncate(fd,sizeof(*mm));

        mm = mmap(NULL,sizeof(*mm),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        close(fd);
        unlink("mt_testt");

        //mm = mmap(NULL,sizeof(*mm),PROT_READ|PROT_WRITE,MAP_SHARED,-1,0); //匿名映射方式

        pthread_mutexattr_init(&mm->mutexattr); //初始化mutex属性对象
        pthread_mutexattr_setpshared(&mm->mutexattr,PTHREAD_PROCESS_SHARED);//修改属性为进程间共享

        pthread_mutex_init(&mm->mutex,&mm->mutexattr);

        pid = fork();

        if(pid==0)//子进程
        {
                for(i=0;i<10;i++)
                {
                        pthread_mutex_lock(&mm->mutex);
                        (mm->sum)++;
                        printf("child num++ %d\n",mm->sum);
                        pthread_mutex_unlock(&mm->mutex);
                        sleep(1);
                }
        }
        else if(pid>0)//父进程
        {
                for(i=0;i<10;i++)
                {
                        sleep(1);
                        pthread_mutex_lock(&mm->mutex);
                        (mm->sum)+=2;
                        printf("child num+=2 %d\n",mm->sum);
                        pthread_mutex_unlock(&mm->mutex);
                }
                wait(NULL);
        }

        pthread_mutexattr_destroy(&mm->mutexattr);
        pthread_mutex_destroy*(&mm->mutex);
        munmap(mm,sizeof(*mm));

        return 0;
}

2 使用文件锁进行同步

借助fcntl函数可以实现锁机制。操作文件的进程没有获得锁时,可以打开,但是无法执行read,write等操作。

代码示例:

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h> //fcntl
#include <unistd.h> //close/exit

void sys_err(const char * str)
{
        perror(str);
        exit(1);
}

int main(int argc,char * argv[])
{
        int fd;
        struct flock f_lock;

        if(argc<2)
        {
                sys_err("user filename as arg1");
        }

        if((fd=open(argv[1],O_RDWR))<0)
        {
                sys_err("open error!");
        }

        f_lock.l_type = F_RDLCK;//读锁
        f_lock.l_whence = SEEK_SET;//从文件开始位置计算偏移量
        f_lock.l_start = 0; // 变异量为0
        f_lock.l_len = 0; //加锁长度为整个文件

        fcntl(fd,F_SETLKW,&f_lock);
        printf("lock\n");
        sleep(10);

        f_lock.l_type = F_UNLCK; //解锁
        fcntl(fd,F_SETLKW,&f_lock);
        printf("unlock");

        close(fd);

        return 0;

}

在两个不同的终端中同时起该程序。发现,一个终端可以立即获得文件锁,而另一个在该程序释放锁之后才能继续操作文件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值