并发:
多个任务在同一个 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;
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b21e4845e0e8e2ea4fe2eebeaee77b8c.png)
加锁,让进程间互斥解决问题
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/42a84869582d9044444aec9a1abc917d.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b59415dd452d61cda149b6c053862152.png)
gTick–不是原子操作
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/671cb1fa8a21740dc8f407475ade3e31.png)
死锁
在多任务系统下,当一个或多个进程等待系统资源,而资源又被进程本身或其它进程占用时,就形成了死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
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上;