互斥锁、条件变量、读写锁、信号量

互斥锁

互斥锁指代相互排斥,它是最基本的同步方式。互斥锁用于保护临界区,以保证任何时刻只有一个线程在执行其中的代码(假设互斥锁由多个线程共享),或者任何时刻只有一个进程在执行其中的代码。

1. 互斥锁类型: 

创建一把锁:pthread_mutex_t mutex;

2. 互斥锁的特点: 

多个线程访问共享数据的时候是串行的

3. 使用互斥锁缺点?

效率低

4. 互斥锁的使用步骤:

创建互斥锁:pthread_mutex_t mutex;
初始化这把锁:pthread_mutex_init(&mutex, NULL); -- mutex = 1
寻找共享资源:
操作共享资源的代码之前加锁

之后进行解锁

5. 互斥锁相关函数:

初始化互斥锁
pthread_mutex_init(
//把锁的地址传进来
pthread_mutex_t *restrict mutex, //restrict使其它指针不能指向这把锁
const pthread_mutexattr_t *restrict attr//锁的性质,一般为NULL

);

销毁互斥锁

pthread_mutex_destroy(pthread_mutex_t *mutex); 

加锁
pthread_mutex_lock(pthread_mutex_t *mutex);
mutex:
没有被上锁,当前线程会将这把锁锁上

被锁上了:当前线程阻塞。锁被打开之后,线程解除阻塞

尝试加锁, 失败返回, 不阻塞
pthread_mutex_trylock(pthread_mutex_t *mutex);
没有锁上:当前线程会给这把锁加锁
如果锁上了:不会阻塞,返回
if(pthread_mutex_trylock(&mutex)==0)
{
// 尝试加锁,并且成功了
// 访问共享资源
}
else
{
// 错误处理
// 或者 等一会,再次尝试加锁

}

解锁

pthread_mutex_unlock(pthread_mutex_t *mutex);

应用代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>

#define MAX 10000
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>

#define MAX 10000
// 全局变量
int number;

//创建一把互斥锁
pthread_mutex_t mutex;

// 线程处理函数
void* funcA_num(void* arg)
{
        for(int i=0; i<MAX; ++i)
        {
                //访问全局变量之前加锁
                //如果mutex被锁上了,代码阻塞在当前位置
                pthread_mutex_lock(&mutex);
                int cur = number;
                cur++;
                number = cur;
                printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
                //解锁
                pthread_mutex_unlock(&mutex);
                usleep(10);
        }

        return NULL;
}

void* funcB_num(void* arg)
{
        pthread_mutex_lock(&mutex);
        for(int i=0; i<MAX; ++i)
        {
                int cur = number;
                cur++;
                number = cur;
                printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
                pthread_mutex_unlock(&mutex);
                usleep(10);
        }

        return NULL;
}
//共享资源是全局变量number
int main(int argc, const char* argv[])
{
        pthread_t p1, p2;

        //初始化互斥锁
        //第一个参数是锁的地址,第二个参数是锁的属性,一般为NULL
        pthread_mutex_init(&mutex,NULL);

        // 创建两个子线程
        pthread_create(&p1, NULL, funcA_num, NULL);
        pthread_create(&p2, NULL, funcB_num, NULL);

        // 阻塞,资源回收
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);

        //释放互斥锁资源
        pthread_mutex_destroy(&mutex);
        return 0;
}

读写锁

互斥锁把试图进入临界区的所有其它线程都阻塞注。然而有时候我们希望在读某个数据与修改某个数据之间作区分。读写锁就是在用于读与写之间作区分。读写锁的分配规则如下:

<1>只要没有线程持有某个给定的读写锁用于读或用于写时,那么任意数目的线程可以持有该读写锁用于读。

<2>仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写。

1.读写锁的特性:

线程A加读锁成功, 又来了三个线程, 做读操作, 可以加锁成功

读共享 - 并行处理

线程A加写锁成功, 又来了三个线程, 做读操作, 三个线程阻塞

写独占

线程A加读锁成功, 又来了B线程加写锁阻塞, 又来了C线程加读锁阻塞
读写不能同时
写的优先级高

2.读写锁场景练习:

线程A加写锁成功, 线程B请求读锁

线程B阻塞

线程A持有读锁, 线程B请求写锁

线程B阻塞

线程A拥有读锁, 线程B请求读锁

线程B加锁成功

线程A持有读锁, 然后线程B请求写锁, 然后线程C请求读锁
B阻塞,c阻塞 - 写的优先级高
A解锁,B线程加写锁成功,C继续阻塞

B解锁,C加读锁成功

线程A持有写锁, 然后线程B请求读锁, 然后线程C请求写锁
BC阻塞
A解锁,C加写锁成功,B继续阻塞

C解锁,B加读锁成功

3.读写锁的适用场景?

互斥锁 - 读写串行
读写锁:读:并行 写:串行

程序中的读操作>写操作的时候

4.主要操作函数

初始化读写锁
pthread_rwlock_init(
pthread_rwlock_t *restrict rwlock, 
const pthread_rwlockattr_t *restrict attr 

);

销毁读写锁

pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加读锁

pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

尝试加读锁
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
加锁成功:0

失败:错误号

加写锁

pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

尝试加写锁

pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

解锁
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

应用代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>

int number=0;
//创建一个读写锁
pthread_rwlock_t lock;
void* write_func(void* arg)
{       
        //循环写
        while(1)
        {       
                //加写锁
                pthread_rwlock_wrlock(&lock);
                number++;
                printf("== write:%lu,%d\n",pthread_self(),number);
                //解锁
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}

void* read_func(void* arg)
{       
        while(1)
        {       
                //加读锁
                pthread_rwlock_rdlock(&lock);
                printf("== read:%lu,%d\n",pthread_self(),number);
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}

int main(int argc,const char* argv[])
{
        pthread_t p[8];
        //创建3个写线程
        for(int i=0;i<3;++i)
        {
                pthread_create(&p[i],NULL,write_func,NULL);
        }
        //创建5个读线程
        for(int i=3;i<8;++i)
        {
                pthread_create(&p[i],NULL,read_func,NULL);
        }

        //阻塞回收子线程的pcb
        for(int i=0;i<8;++i)
        {
                pthread_join(p[i],NULL);
        }
        //释放读写锁资源
        pthread_rwlock_destroy(&lock);
        return 0;
}

条件变量

互斥锁用于上锁,条件变量则用于等待。这两种不同类型的同步都非常重要。

每个条件变量总是有一个互斥锁与之关联。调用pthread_cond_wait等待某个条件为真时,还会指定其条件变量的地址和所关联的互斥锁的地址。

1.条件变量是锁吗

不是锁, 但是条件变量能够阻塞线程
使用条件变量 + 互斥量
互斥量: 保护一块共享数据
条件变量: 引起阻塞

2.条件变量的两个动作

条件不满足, 阻塞线程
当条件满足, 通知阻塞的线程开始工作

3.条件变量的类型: 

pthread_cond_t cond;

4.主要函数:

初始化一个条件变量
pthread_cond_init(
pthread_cond_t *restrict cond, 
const pthread_condattr_t *restrict attr
);
销毁一个条件变量
pthread_cond_destroy(pthread_cond_t *cond);
阻塞等待一个条件变量

pthread_cond_wait(

            pthread_cond_t *restrict cond, 

   pthread_mutex_t *restrict mutex
);
    阻塞线程
    将已经上锁的mutex解锁
    该函数解除阻塞,会对互斥锁加锁
限时等待一个条件变量
pthread_cond_timedwait(
pthread_cond_t *restrict cond, 
pthread_mutex_t *restrict mutex, 
const struct timespec *restrict abstime
);
唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t *cond);
唤醒全部阻塞在条件变量上的线程

pthread_cond_broadcast(pthread_cond_t *cond);

应用代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>

typedef struct node
{       
        int data;
        struct node* next;
}Node;

//永远指向链表头部的指针
Node* head=NULL;

//创建一个互斥锁-线程同步
pthread_mutex_t mutex;
//条件变量类型的变量-阻塞线程
pthread_cond_t cond;

//生产者
void* producer(void* arg)
{
        while(1)
        {
                //创建一个链表的节点
                Node* pnew=(Node*)malloc(sizeof(Node));
                //节点的初始化
                pnew->data=rand()%1000;//0-999

                //使用互斥锁保护共享数据
                pthread_mutex_lock(&mutex);
                //指针域
                pnew->next=head;
                head=pnew;
                printf("=====produce:%lu,%d\n",pthread_self(),pnew->data);
                pthread_mutex_unlock(&mutex);

                //通知阻塞的消费者线程,解除阻塞
                pthread_cond_signal(&cond);

                sleep(rand()%3);
        }
        return NULL;
}

//消费者
void* customer(void* arg)
{
        while(1)
        {
                //使用互斥锁
                pthread_mutex_lock(&mutex);
                //判断链表是否为空
                if(head==NULL)
                {
                        //continue;对资源造成浪费,一直在这里continue
                        //线程阻塞
                        //该函数会对互斥锁解锁
                        pthread_cond_wait(&cond,&mutex);
                }
                //链表不为空,删掉一个节点-删除头节点
                Node* pdel=head;
                head=head->next;
                printf("=====customer:%lu,%d\n",pthread_self(),pdel->data);
                free(pdel);
                pthread_mutex_unlock(&mutex);

                sleep(rand()%3);
        }
        return NULL;
}
int main(int argc,const char* argv[])
{
        pthread_t p1,p2;
        //init
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);

        //创建生产者线程
        pthread_create(&p1,NULL,producer,NULL);
        //创建消费者线程
        pthread_create(&p2,NULL,customer,NULL);

        //阻塞回收子线程
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

        return 0;
}
                                                                                                                                                      

信号量

信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。常见的有三种:

Posix有名信号量:可用于进程或线程间的同步。

Posix基于内存的信号量(无名信号量):存放在共享内存区,可用于进程或线程间的同步。

System V信号量:在内核中维护,可用于进程或线程间的同步。

下面说一下无名信号量

1.头文件 

#include<semaphore.h>

2.信号量类型

sem_t sem;

加强版的互斥锁

3.主要函数

初始化信号量
sem_init(sem_t *sem, int pshared, unsigned int value);
0 - 线程同步
1 - 进程同步
value - 最多有几个线程操作共享数据 - 5
销毁信号量
sem_destroy(sem_t *sem);
加锁  
sem_wait(sem_t *sem);
调用一次相当于对sem做了--操作
如果sem值为0, 线程会阻塞
尝试加锁 
sem_trywait(sem_t *sem);
sem == 0, 加锁失败, 不阻塞, 直接返回
限时尝试加锁
sem_timedwait(sem_t *sem, xxxxx);
解锁
sem_post(sem_t *sem);

对sem做了++操作

互斥锁和信号量之间的区别:

作用域

信号量: 进程间或线程间

互斥锁: 线程间
上锁时 

信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一

互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源

利用信号量与互斥锁解锁生产者、消费者问题

生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

代码

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
#include <semaphore.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
        do \
{ \
        perror(m); \
        exit(EXIT_FAILURE); \
}while(0)

#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 5
#define BUFFSIZE 10
//定义环形缓冲区
int g_buffer[BUFFSIZE];
//从0位置开始生产产品
unsigned short in=0;
//从0位置开始消费产品
unsigned  short out=0;
//当前正在生产的产品id
unsigned short produce_id=0;
//当前正在消费的产品id
unsigned short consume_id=0;
//两个信号量
sem_t g_sem_full;
sem_t g_sem_empty;
//一个互斥锁
pthread_mutex_t g_mutex;
//线程数量
pthread_t g_thread[CONSUMERS_COUNT+PRODUCERS_COUNT];

void* consume(void *arg)
{
        int num=(int)arg;
        int i;
        //消费者不停的消费
        while(1)
        {
                printf("消费者(线程)%d wait buffer not empty\n",num);
                //等待一个空的信号量,直到仓库不空
                sem_wait(&g_sem_empty);
                pthread_mutex_lock(&g_mutex);

                //打印输出仓库的当前状态
                for(i=0;i<BUFFSIZE;i++)
                {
                        printf("%02d   ",i);
                        if(g_buffer[i]==-1)
                                printf("%s","null");
                        else
                                printf("%d",g_buffer[i]);
                        if(i==out)
                                printf("\t<--consume");
                        printf("\n");
                }

                consume_id=g_buffer[out];
                printf("消费者(线程)%d begin consume product %d\n",num,consume_id);
                g_buffer[out]=-1;
                out=(out+1)%BUFFSIZE;
                printf("消费者(线程)%d end consume product %d\n",num,consume_id);

                pthread_mutex_unlock(&g_mutex);
                sem_post(&g_sem_full);
                sleep(5);
        }
        return NULL;
}
void* produce(void *arg)
{
        //num是生产者的编号
        int num=(int)arg;
        int i;
        //生产者不停的生产
        while(1)
        {
                printf("生产者(线程)%d wait buffer not full\n",num);
                //最多由g_sem_full个线程操作共享数据
                sem_wait(&g_sem_full);
                //互斥锁
                pthread_mutex_lock(&g_mutex);

                //打印输出仓库的当前状态
                for(i=0;i<BUFFSIZE;i++)
                {
                        printf("%02d   ",i);
                        if(g_buffer[i]==-1)
                                printf("%s","null");
                        else
                                printf("%d",g_buffer[i]);
                        if(i==in)
                                printf("\t<--produce");
                        printf("\n");
                }

                printf("生产者(线程)%d begin produce product %d\n",num,produce_id);
                g_buffer[in]=produce_id;
                in=(in+1)%BUFFSIZE;
                printf("生产者(线程)%d end produce product %d\n",num,produce_id++);

                pthread_mutex_unlock(&g_mutex);
                sem_post(&g_sem_empty);
                sleep(1);
        }
        return NULL;
}
//一个互斥锁
int main(void)
{
        int i;
        //对仓库进行初始化
        for(i=0;i<BUFFSIZE;i++)
                g_buffer[i]=-1;

        //初始化信号量
        sem_init(&g_sem_full,0,BUFFSIZE);
        sem_init(&g_sem_empty,0,0);
        //初始化互斥锁
        pthread_mutex_init(&g_mutex,NULL);
        //创建若干个消费者
        for(i=0;i<CONSUMERS_COUNT;i++)
                pthread_create(&g_thread[i],NULL,consume,(void*)i);
        //创建若干个生产者
        for(i=0;i<PRODUCERS_COUNT;i++)
                /*
                thread:返回线程ID
                attr:设置线程的属性,attr为NULL表示使用默认属性
                start_routine:是个函数地址,线程启动后要执行的函数
                arg:传给线程启动函数的参数
                */
                pthread_create(&g_thread[CONSUMERS_COUNT+i],NULL,produce,(void*)i);
        //等待这些线程的退出
        for(i=0;i<CONSUMERS_COUNT+PRODUCERS_COUNT;i++)
                pthread_join(g_thread[i],NULL);

        sem_destroy(&g_sem_full);
        sem_destroy(&g_sem_empty);
        pthread_mutex_destroy(&g_mutex);

        return 0;
}

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80586094