线程间的同步和互斥

并发:

多个任务在同一个 CPU 核上按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
针对 CPU 内核来说,任务仍然是按细粒度的串行执行。

并行:

一般指并行计算,是说同一时刻有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,
或者多个CPU上,或者多个物理主机甚至多个网络中.

同步:

进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。
进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。
具有同步关系的一组并发进程相互发送的信息称为消息或事件。

其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。

异步:

与同步相对应,异步指的是让CPU暂时搁置当前请求的响应,处理下一个请求,
当通过轮询或其他方式得到回调通知后,开始运行。多线程将异步操作放入另一线程中运行,
通过轮询或回调方法得到完成通知,但是完成端口,由操作系统接管异步操作的调度,
通过硬件中断,在完成时触发回调方法,此方式不需要占用额外线程。
一个小故事

故事:小A烧开水。

出场人物:小A出场道具:普通水壶(放在煤气灶上的那种,为了方便简称:水壶);会响的水壶(水烧开了会响的那种,简称:响壶)。故事目的:小A要拿开水泡咖啡

小A为了实现目的,指定了4个计划:

1、用水壶烧水,并且站在煤气灶旁边,啥事不干,两眼直勾勾的盯着水壶,等水烧开。烧开后就去泡咖啡。同步阻塞

假设烧水和泡咖啡是在同一个线程中执行。

2、仍然用水壶煮水,不过此时不再傻傻得站在那里看水开没开,而是去玩局LOL,每当自己死了,就过来看看水开了没有。如果水开了就去泡咖啡。同步非阻塞

假设这里玩LOL,是另一个线程运行的。

3、动用响壶烧水,仍然站在煤气灶旁边,不过此时不两眼直勾勾的盯着壶了,而是听响,因为响壶水开时会用响声通知小A。异步阻塞

4、在计划3的基础上,小A不站在煤气灶旁边了,而是去玩局LOL,等听到响壶的声音提醒后,再去跑咖啡。异步非阻塞

阻塞

阻塞的概念往往伴随着线程。阻塞一般是指:在调用结果返回之前,
当前线程会被挂起。调用线程只有在得到结果之后才会被唤醒执行后续的操作。

非阻塞

那么非阻塞,毫无疑问是阻塞的反向操作。非阻塞式的调用指:在结果没有返回之前,该调用不会阻塞住当前线程。

是不是感觉阻塞/非阻塞和同步/异步有异曲同工的地方?

其实,这两者存在本质的区别,面向的对象是不同的。

重点

阻塞/非阻塞:
进程/线程需要操作的数据如果尚未就绪,是否妨碍了当前进程/线程的后续操作。

同步/异步:
数据如果尚未就绪,是否需要等待数据结果。

互斥锁(互斥量)

锁机制是同一时刻只允许一个线程执行一个关键部分的代码。

互斥量用pthread_mutex_t数据类型来表示。
两种方式初始化,第一种:赋值为常量PTHREAD_MUTEX_INITIALIZER;
第二种,当互斥量为动态分配是,使用pthread_mutex_init函数进行初始化,使用pthread_mutex_destroy函数销毁。

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *__mutex,
__const pthread_mutexattr_t *__mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *__mutex);
返回值:成功-0,失败-错误编号

加解锁

加锁调用pthread_mutex_lock,解锁调用pthread_mutex_unlock。
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *__mutex);
int pthread_mutex_unlock(pthread_mutex_t *__mutex);

案例


mutex1.c


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

int gTick = 100;

#define TRDNUM 4
pthread_mutex_t mutex;
void* trdfun(void* args)
{
char* pArgs = (char*)args;

while(1)
{
    pthread_mutex_lock(&mutex);


    if(gTick > 0)
    {
        //1s=1000ms=1000*1000us
        usleep(1000);

        printf("%s Selled %d ticket\n",pArgs,gTick);

        gTick--;
        pthread_mutex_unlock(&mutex);
    }
    else
    {
        pthread_mutex_unlock(&mutex);
        break;
    }
}

if(args)
{
    free(args);
    args = NULL;
}

}

int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t trdArray[TRDNUM];

int i = 0;

for(i=0;i<TRDNUM;i++)
{
    char *buffer = (char*)malloc(32*sizeof(char));
    sprintf(buffer,"thread%d",i+1);

    pthread_create(&trdArray[i],NULL,trdfun,(void *)buffer);

}

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

pthread_mutex_destroy(&mutex);
return 0;

}
在这里插入图片描述
加锁,让进程间互斥解决问题
在这里插入图片描述
在这里插入图片描述
gTick–不是原子操作
在这里插入图片描述
死锁

在多任务系统下,当一个或多个进程等待系统资源,而资源又被进程本身或其它进程占用时,就形成了死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

1)互斥条件
2)请求和保持条件
3)不剥夺条件
4)环路等待条件

条件变量

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制

条件变量是用来等待线程中某个条件出现,而不是上锁的,条件变量通常和互斥锁一起使用

使用pthread_cond_wait等待条件为真。
pthread_cond_wait(pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
这里需要注意的是,调用pthread_cond_wait传递的互斥量已锁定,
pthread_cond_wait将调用线程放入等待条件的线程列表,然后释放互斥量,在pthread_cond_wait返回时,再次锁定互斥量。

唤醒线程
pthread_cond_signal唤醒等待该条件的某个线程
int pthread_cond_signal(pthread_cond_t *__cond);

pthread_cond_broadcast唤醒等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *__cond)

sched_yield();

导致当前线程放弃CPU,重新排队,别的线程得到执行

别的线程(和当前线程的优先级的相同或更高的优先级)

读写锁

允许多个线程同时读,只能有一个线程同时写。适用于读的次数远大于写的情况。
读写锁初始化:

#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *__restrict __rwlock,
__const pthread_rwlockattr_t *__restrict
__attr);
int pthread_rwlock_destroy(pthread_rwlock_t *__rwlock);
返回值:成功–0,失败-错误编号

加锁,这里分为读加锁和写加锁。

读加锁:
int pthread_rwlock_rdlock(pthread_rwlock_t *__rwlock)
写加锁:
int pthread_rwlock_wrlock(pthread_rwlock_t *__rwlock)
解锁用同一个函数:
int pthread_rwlock_unlock(pthread_rwlock_t *__rwlock)
案例:
在这里插入图片描述

POSIX信号量

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

销毁
#include <semaphore.h>
int sem_destroy(sem_t *sem)

命名信号量
初始化
#include <semaphore.h>
sem_t *sem_open(const char name, int oflag, / mode_t mode, unsigned int value */);

关闭与销毁
int sem_close(sem_t *sem);
int sem_unlink(const char *name);

等待信号量(P操作)
#include <semaphore.h>
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);

发布信号量(V操作)
#include <semaphore.h>
int sem_post(sem_t *sem);

取值操作
#include<semaphore.h>
int sem_getvalue(sem_t sem, int *val);

销毁
功能:销毁由sem指向的匿名信号量。
原型:
#include <semaphore.h>
int sem_destroy(sem_t *sem);

使用流程
sem_init
sem_wait—P操作
sem_post—V操作
sem_destroy

总结

读写锁

读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。

与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
读写锁本质上是一种自旋锁

互斥锁和自旋锁的区别

互斥锁阻塞时,出让cpu,线程要重新排队(运行队列)执行
自旋锁阻塞时,不出让cpu。此时cpu不能做别的事情

CPU亲和性(了解)

CPU的亲和性,就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到
其他处理器,也称为CPU关联性;再简单的点的描述就将指定的进程或线程
绑定到相应的CPU上;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值