今天是学习嵌入式的第34天

今天说一下线程间的通信

Linux线程间通信与同步

同一进程下所有线程,共享全局变量、堆、文件描述符。线程天然共享内存,但会出现数据竞争。线程通信主要靠共享内存 + 同步工具。管道、消息队列、shm共享内存属于进程间IPC,线程虽然也能用,但太重,线程一般不用。

用到头文件:#include <pthread.h>

1.裸用全局变量(错误例子)

int cnt = 0;

void* thr_func(void *arg)

{

int i;

for(i = 0; i < 10000; i++)

{

cnt++;

}

return NULL;

}

开两个线程,预期20000,实际结果偏小。

原因cnt++不是单条CPU指令:读,自增,写回。线程切换会互相覆盖,数据丢失。必须加锁保护临界区。

2.互斥锁 pthread_mutex_t

作用:同一时间只允许一个线程进入临界代码段。

接口:

1)pthread_mutex_init() 动态初始化锁

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

attr传NULL代表默认属性。

静态初始化直接写:pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

2)pthread_mutex_lock(pthread_mutex_t *mutex);

拿锁。锁被别人占着,当前线程阻塞休眠,直到拿到锁才返回。

3)pthread_mutex_trylock(pthread_mutex_t *mutex);

尝试加锁,非阻塞。拿不到直接返回EBUSY,不会睡觉。不建议死循环trylock,浪费CPU。

4)pthread_mutex_unlock(pthread_mutex_t *mutex);

解锁。必须是谁加锁谁解锁,不能A线程lock,B线程unlock,行为未定义。

5)pthread_mutex_destroy(pthread_mutex_t *mutex);

销毁锁,释放资源。

正确使用模板:

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

int cnt = 0;

void* thr_func(void *arg)

{

int i;

for(i = 0; i < 10000; i++)

{

pthread_mutex_lock(&mtx);

cnt++;

pthread_mutex_unlock(&mtx);

}

return NULL;

}

坑点:lock之后,如果return、break直接跳出,忘记unlock,直接死锁。

死锁两种常见场景:普通锁重复加锁;A拿锁1等锁2,B拿锁2等锁1。

3.条件变量 pthread_cond_t

互斥锁只管访问互斥,解决不了"等待某个条件成立"。比如生产者放数据,消费者没数据就休眠,不要while死循环轮询占满CPU,就用条件变量。

接口:

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态初始化:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

1)pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

重点:调用wait的时候,线程必须已经持有这把mtx互斥锁。

做两件原子操作:

①线程进入条件变量等待队列休眠

②自动释放mtx锁,别的线程可以抢到锁修改共享数据。

被唤醒之后,函数返回之前会自动重新获取mtx锁,才返回。

重点:wait睡醒,条件不一定真满足!操作系统会产生虚假唤醒。判断条件必须用while,绝对不能用if。

2)pthread_cond_signal(pthread_cond_t *cond);

唤醒至少一个阻塞在cond上的线程,叫醒其中一个。

3)pthread_cond_broadcast(pthread_cond_t *cond);

广播,把所有睡在这个cond上面的线程全部唤醒。

4)pthread_cond_destroy(pthread_cond_t *cond);销毁条件变量。

极简生产者消费者伪代码:

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int buf = 0;

//消费者

void* consumer(void* arg)

{

pthread_mutex_lock(&mtx);

while(buf == 0) //必须while,不能if

{

pthread_cond_wait(&cond, &mtx);

}

//走到这里buf有数据

printf("消费:%d\n",buf);

buf = 0;

pthread_mutex_unlock(&mtx);

return NULL;

}

//生产者

void* producer(void* arg)

{

pthread_mutex_lock(&mtx);

buf = 100;

pthread_cond_signal(&cond); //叫醒消费者

pthread_mutex_unlock(&mtx);

return NULL;

}

4.读写锁 pthread_rwlock_t

场景:读多写少。很多线程可以同时读;写的时候,所有读、写全部阻塞。读共享,写独占。

接口:

静态初始化:pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

动态:pthread_rwlock_init()

pthread_rwlock_rdlock(&rwlock); 加读锁,多个线程可以同时拿读锁

pthread_rwlock_wrlock(&rwlock); 加写锁,独占

pthread_rwlock_tryrdlock(&rwlock);尝试读锁非阻塞

pthread_rwlock_trywrlock(&rwlock);尝试写锁非阻塞

pthread_rwlock_unlock(&rwlock);释放锁

pthread_rwlock_destroy(&rwlock);销毁

坑:大量读锁持续占用,写线程会一直拿不到锁,写饥饿。

5.自旋锁 pthread_spinlock_t

普通互斥锁拿不到就休眠,切换线程有系统开销。自旋锁拿不到锁的时候,CPU空转循环忙等,不切换上下文。

适合:锁保持时间特别短。如果锁占用时间长,CPU直接跑满,不适合。

接口:

pthread_spin_init()初始化

pthread_spin_lock()自旋加锁,拿不到就原地循环

pthread_spin_trylock()非阻塞尝试拿

pthread_spin_unlock()解锁

pthread_spin_destroy()销毁

6.POSIX无名信号量 sem_t(线程同步常用)

头文件 #include <semaphore.h>

注意这是POSIX信号量,不是system V的信号量。可以用于线程同步,也可以用于进程。

接口:

sem_t sem;

int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared=0,用于同一个进程内部线程;value信号量初始值。

sem_wait(sem_t *sem); 信号量大于0就减1继续走;等于0线程阻塞休眠。

sem_trywait(sem_t *sem);非阻塞版本。

sem_post(sem_t *sem);信号量+1,唤醒等待的线程。

sem_destroy(sem_t *sem);销毁无名信号量。

可以实现生产者消费者,也可以做锁。信号量可以记录资源计数,互斥锁只能0/1。

7.线程也能使用信号

pthread_kill(线程tid, sig);给指定线程发送信号。

注意:信号是进程全局资源,多线程下信号处理很容易乱,线程开发尽量少用信号做通信。

总结区分:

1.互斥锁:保护临界资源,同一时间只一个线程访问。

2.条件变量:配合互斥锁,做等待‑唤醒逻辑,解决生产者消费者。必须while判断防虚假唤醒。

3.读写锁:读多写少场景。

4.自旋锁:锁持有极短,避免线程切换开销;不能长时间占用。

5.sem_t信号量:做资源计数同步。

重点提醒:

线程之间天然共享内存,这是线程最主要通信方式。上面全部工具,本质都是用来解决"共享内存带来的数据竞争问题"。

管道、FIFO、消息队列、systemV共享内存属于进程间通信,线程能用,但是开销大,线程一般优先用pthread系列同步接口。

相关推荐
2501_9318197019 分钟前
桂柳方言标注公司错误标注溯源追踪体系搭建诉求落地 桂柳方言标注公司学习信实翻译全链路追踪技术
学习·语音识别
cuijiecheng201836 分钟前
从 GinormoTime 到 Rocky Linux:25fps LTC 完整搭建与验证实战
linux·运维·服务器
GeW39 分钟前
告别盲目刷题!RHCE高效备考路线图,助你一次Pass
linux
Asum1ta1 小时前
K8s 学习环境搭建:Python 与 PyCharm 开发环境配置指南
python·学习·kubernetes
知识汲取者1 小时前
FastAPI 学习教程(写给想学 FastAPI 的 Java 工程师的)
python·学习·fastapi
H_oRIZoN_1 小时前
Linux入门DAY35(TCP粘包问题与HTTP)
linux·tcp/ip·http
好评1241 小时前
【Linux】传输层协议TCP
linux·网络·tcp/ip
飞Link1 小时前
动作方法中的分割与过度分割方法全解析(含代码实战与踩坑案例)
人工智能·python·算法·计算机视觉
Shell运维手记1 小时前
Linux mdadm 软 RAID + LVM 完整综合笔记
linux·运维·笔记·5g