Linux多线程(2)-线程间同步的5种方式,一次性说清楚!

线程为什么要同步?

当进程中的多个线程,同时读取一块内存数据,与此同时其中一个或多个线程修改了这块内存数据。这样就会导致不可预期的结果。
因为线程不安全引起的错误往往非常难发现,不能稳定复现,所以在编码的时候就应该事先考虑,会不会有多线程并发的情况,就像我们写完malloc之后就会去判空,然后再memset一样,多线程操作环境,就应该选择互斥锁或其他方式,保护共享资源

问题示意图:
image.png

Linux下常用的线程间同步方式有五种,分别是互斥锁、读写锁、条件变量、自旋锁、屏障

1.互斥锁(mutex)

什么都可以不看,最后的死锁总结一定要看!

1.1 pthread_mutex_init初始化互斥锁

// 动态分配
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);

// 静态分配
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

锁必须采用其中一种来初始化,两种方式使用下来没啥差别

1.2 pthread_mutex_destroy销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);
//成功,返回0;否则,返回错误编号

1.3 pthread_mutex_lock上锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//所有函数的返回值:若成功,返回0;否则,返回错误编号

lock:将一直阻塞,直到拿到锁。如果重复lock而没有unlock则会造成死锁,程序阻塞,这是程序开发中常见的问题。
trylock:尝试上锁,不阻塞,拿到锁则返回0,否则返回错误编号EBUSY

1.4 pthread_mutex_unlock解锁

int pthread_mutex_unlock(pthread mutex_t *mutex);

上锁后,一定要在合适时期进行解锁

1.5 pthread_mutex_timedlock超时锁

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
//返回值:若成功,返回0;否则,返回错误编号

和普通互斥锁的区别是超时锁会有一个最大的超时时间,不会发生死锁的情况
timespec结构体为绝对时间
clock_gettime(CLOCK_REALTIME)获取当前时间,然后秒钟+10,则代表一个10S超时锁。
超时到期,返回ETIMEDOUT

1.6关于死锁问题的血泪总结

这里都是一些个人血和泪的经验~
1)锁的粒度太大,容易出现很多线程阻塞等待锁的情况,导致程序并发性差
而如果锁的粒度太细,过度的锁开销会使系统性能受到影响,代码变的复杂,所以要找到一个平衡
2)在程序开发中,写完lock之后,就要条件反射一般,将unlock写好,防止之后忘记
3)在函数内进行lock之后,一定要谨防函数直接return的情况,return 之前要释放锁,否则下次拿锁会出现死锁的情况。
4)带锁的线程,在使用pthread_cancle强制取消线程时,要对锁进行处理,详情可见
https://blog.csdn.net/qq_43603125/article/details/136918805
5)两把锁一起使用时,切记要注意交叉锁的情况
线程A 拿到了线程锁A 并阻塞等待线程锁B
线程B 拿到了线程锁B 并阻塞等待线程锁A 造成了死锁。
所以如果在调用外部注册进来的回调函数,一定不要带锁去调用,因为不知道外部会如何操作

2.读写锁(rwlock)

读写锁与互斥量类似,但允许更高的并行性,非常适合对数据结构读的次数远大于写的情况

读写锁有三种状态读模式下加锁状态、写模式下加锁状态、不加锁状态
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁

写加锁状态:所有读、写的线程都会阻塞
读加锁状态:所有读操作都能拿到锁,写的线程都会阻塞,直到所有线程释放读锁。当写线程试图拿锁时,锁会阻塞之后的读模式锁请求,可以避免读模式锁长期占用,写锁一直拿不到。

2.1 pthread_rwlock_init 初始化读写锁

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//成功返回0,失败返回错误编号

NULL 代表默认属性
使用读写锁和互斥锁基本一样,首先便要初始化

2.2 pthread_rwlock_destroy 销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//两个函数的返回值:若成功,返回0:否则,返回错误编号

不使用读写锁之后需要销毁

2.3 上锁操作

int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock); //上读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //上写锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock); //尝试上读锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //尝试上写锁
int pthread_rwlock_timedrdlock(pthread rwlock t *restrict rwlock,
const struct timespec *restrict tsptr); // 上读锁的超时版本
int pthread rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
const struct timespec*restrict tsptr); // 上写锁的超时版本
//所有函数的返回值:若成功,返回0;否则,返回错误编号

和互斥锁一致,读写锁同样有普通、尝试、超时的三个版本。超时到期,返回ETIMEDOUT。
根据业务需求来选择合适的锁

2.4 解锁操作

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//函数的返回值:若成功,返回0;否则,返回错误编号

3.条件变量(cond)

条件变量允许一个线程等待另一个线程满足某个条件后再继续执行,避免线程无谓的轮询和忙等待,提高了系统的响应能力和效率,为了避免竞争,需要搭配互斥锁一起使用

主要包括两个动作:1)A线程等待条件变量成立而挂起 2)B线程使条件成立 <给出条件成立信号>

3.1 pthread_cond_init初始化条件变量

//静态初始化
static cond = PTHREAD_COND_INITALIZER;

//动态初始化
pthread_cond_t cond;
pthread_cond_init(&cond;)

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

3.2 pthread_cond_destroy反初始化条件变量

int pthread_cond_destroy(pthread_cond_t *cond);
//两个函数的返回值:若成功,返回0:否则,返回错误编号

3.3 pthread_cond_wait等待条件变量

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex.t *restrict mutex);
int pthread_cond timedwait(pthread_cond t *restrict cond,
pthread_mutex_t*restrict mutex,
const struct timespec *restrict tsptr);
//两个函数的返回值:若成功,返回0:否则,返回错误编号

带超时版本:超时到期条件未出现,pthread_cond_timewait将重新获取互斥量,然后返回错误ETIMEDOUT

3.4 pthread_cond_signal唤醒线程

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
// 两个函数的返回值:若成功,返回0:否则,返回错误编号

signal至少唤醒一个等待该条件的线程
broadcast 唤醒等待该条件的所有线程

3.5 关于条件变量的血泪总结

这些都是实践+理论得到的血汗经验~,学到了赶紧给我点个收藏
当线程A调用pthread_cond_wait 函数,自动把线程A放到等待条件的线程列表上。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
image.png
上述代码中是以前写的,现在回顾看起来有一些小问题,应该把pthread_cond_signal放到lock和unlock中间。

这里会涉及到一个虚假唤醒的问题,假设多线程等待同一个条件变量,而信号发生的地方只有一个,那么在信号发生后,可能会同时唤醒多个线程,所以上面的代码pthread_cond_wait醒来后,再次对workq进行了检查,防止虚假唤醒!

4.自旋锁(spin)

自旋锁和互斥锁类似
**自旋锁是一种非阻塞锁:**需要拿锁时,CPU会不停的去轮询,不停的尝试获取自旋锁。适用于占用时间短的情况,节省线程调度的时间成本。
**互斥锁是一种阻塞锁:**需要拿锁时,若无法获取到锁,线程会被挂起,当其他线程释放互斥量,操作系统会激活被挂起的线程。

4.1 pthread_spin_init初始化

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

和互斥锁、读写锁一样,使用之前都需要进行初始化

4.2 pthread_spin_destroy销毁

int pthread_spin_destroy(pthread_spinlock_t *lock);

4.3 pthread_spin_lock上锁

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

普通上锁以及尝试上锁版,若尝试发现无法上锁,则直接返回错误编号EBUSY

4.4 pthread_spin_unlock解锁

int pthread_spin_unlock(pthread_spinlock_t *lock);
//所有函数的返回值:若成功,返回0:否则,返回错误编号

5.屏障(barrier)

屏障:用户协调多个线程并行工作的同步机制
简单来说,屏障允许每个线程等待,直到所有的合作线程都到达某一点,再统一执行某个任务,适用于高并发的情况

如果只用一个线程进行800万个数的排序,非常耗性能
但在8核处理器系统上,分成8个线程去做,每个线程处理完毕都wait 等待其他线程
最后再到一个线程上进行合并,速度能提升好几倍!

5.1 pthread_barrier_init初始化

int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr,
signed int count);
// 成功返回0或PTHREAD_BARRIER_SERIAL_WAIT(说明是主线程),失败返回错误编号

count:需要等待的个数,当pthread_barrier_wait的调用个数到达count后,所有线程都将被唤醒
attr:屏障属性

5.2 pthread_barrier_destroy 反初始化

int pthread_barrier_destroy(pthread_barrier_t *barrier)
//返回值:若成功,返回0;否则,返回错误编号

5.3 pthread_barrier_wait到达屏障

int pthread_barrier_wait(pthread_barrier_t *barrier);
//返回值:若成功,返回0,否则,返回错误编号

调用后线程将阻塞,表明已经到达屏障,正在等待其他线程,调用这个函数后,wait_count–,从init设定的wait_count减到0时,则所有线程被唤醒。

屏障的使用是可以复用的,也就是说当一次所有线程都被唤醒后,之后可以继续使用这个屏障,计数重新来到init的count;

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程通信篇 Linux网络编程之线程Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程通信篇 23进程通信介绍(一) 进程同步与进程互斥 进程通信目的 进程通信发展 进程通信分类 进程共享信息的三方式 IPC对象的持续性 24进程通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程实现 线程池性能分析 线程实现 网络编程, Linux
同步概念 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之规定一个共同的时参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等 而,编程中、通信中所同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。 线程同步 同步即协同步调,按预定的先后次序运行。 线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。 举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000 举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执行结束,内存中的100字节,既不是全1,也不是全0。 产生的现象叫做“与时有关的错误”(time related)。为了避免这数据混乱,线程需要同步。 “同步”的目的,是为了避免数据混乱,解决与时有关的错误。实际上,不仅线程需要同步,进程、信号等等都需要同步机制。 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。 数据混乱原因: 1. 资源共享(独享资源则不会) 2. 调度随机(意味着数据访问会出现竞争) 3. 线程缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。 互斥量mutex Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 资源还是共享的,线程也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时有关的错误也不会再产生了。 但,应注意:同一时刻,只能有一个线程持有该锁。 当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。 所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 主要应用函数: pthread_mutex_init函数 pthread_mutex_destroy函数 pthread_mutex_lock函数 pthread_mutex_trylock函数 pthread_mutex_unlock函数 以上5个函数的返回值都是:成功返回0, 失败返回错误号。 pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。 pthread_mutex_t mutex; 变量mutex只有两取值1、0。 pthread_mutex_init函数 初始化一个互斥锁(互斥量) ---> 初值可看作1 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参1:传出参数,调用时应传 &mutex restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程共享)。 参APUE.12.4同步属性 1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL) pthread_mutex_destroy函数 销毁一个互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); pthread_mutex_lock函数 加锁。可理解为将mutex--(或-1) int pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock函数 解锁。可理解为将mutex ++(或+1) int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_trylock函数 尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 加锁与解锁 lock与unlock: lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。 unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。 例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。 可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++ lock与trylock: lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。 加锁步骤测试: 看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时有关的错误,造成数据混乱: #include #include #include void *tfn(void *arg) { srand(time(NULL)); while (1) { printf("hello "); sleep(rand() % 3); /*模拟长时操作共享资源,导致cpu易主,产生与时有关的错误*/ printf("world\n"); sleep(rand() % 3); } return NULL; } int main(void) { pthread_t tid; srand(time(NULL)); pthread_create(&tid, NULL, tfn, NULL); while (1) { printf("HELLO "); sleep(rand() % 3); printf("WORLD\n"); sleep(rand() % 3); } pthread_join(tid, NULL); return 0; } 【mutex.c】 【练习】:修改该程序,使用mutex互斥锁进行同步。 1. 定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry 2. 两个线程while中,两次printf前后,分别加lock和unlock 3. 将unlock挪至第二个sleep后,发现交替现象很难出现。 线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。 所以在这两行代码之失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。 4. main 中加flag = 5 将flg在while中-- 这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。 5. main 中加pthread_cancel()将子线程取消。 【pthrd_mutex.c】 结论: 在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。 死锁 1. 线程试图对同一个互斥量A加锁两次。 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁 【作业】:编写程序,实现上述两死锁现象。 读写锁 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。 读写锁状态: 一把读写锁具备三状态: 1. 读模式下加锁状态 (读锁) 2. 写模式下加锁状态 (写锁) 3. 不加锁状态 读写锁特性: 1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。 2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。 3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 读写锁非常适合于对数据结构读的次数远大于写的情况。 主要应用函数: pthread_rwlock_init函数 pthread_rwlock_destroy函数 pthread_rwlock_rdlock函数 pthread_rwlock_wrlock函数 pthread_rwlock_tryrdlock函数 pthread_rwlock_trywrlock函数 pthread_rwlock_unlock函数 以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。 pthread_rwlock_t类型 用于定义一个读写锁变量。 pthread_rwlock_t rwlock; pthread_rwlock_init函数 初始化一把读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 参2:attr表读写锁属性,通常使用默认属性,传NULL即可。 pthread_rwlock_destroy函数 销毁一把读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_rdlock函数 以读方式请求读写锁。(常简称为:请求读锁) int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); pthread_rwlock_wrlock函数 以写方式请求读写锁。(常简称为:请求写锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); pthread_rwlock_unlock函数 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock函数 非阻塞以读方式请求读写锁(非阻塞请求读锁) int pthread_
Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程通信篇 23进程通信介绍(一) 进程同步与进程互斥 进程通信目的 进程通信发展 进程通信分类 进程共享信息的三方式 IPC对象的持续性 24进程通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程实现 线程池性能分析 线程实现
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程通信篇 Linux网络编程之线程Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程通信篇 23进程通信介绍(一) 进程同步与进程互斥 进程通信目的 进程通信发展 进程通信分类 进程共享信息的三方式 IPC对象的持续性 24进程通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35PO
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程通信篇 Linux网络编程之线程Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程通信篇 23进程通信介绍(一) 进程同步与进程互斥 进程通信目的 进程通信发展 进程通信分类 进程共享信息的三方式 IPC对象的持续性 24进程通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信
Linux驱动中的多线程指的是驱动程序中同时执行的多个线程多线程的产生有几个原因:多线程并发访问、抢占式并发访问、中断程序并发访问和SMP(多核)核并发访问。 在编写Linux驱动程序时,我们需要考虑并发和竞争访问问题。并发访问会带来竞争问题,特别是在涉及共享数据段的临界区,我们必须保证一次只有一个线程访问临界区,也就是要保证临界区是原子访问的。这意味着一个访问操作必须是一个完整的步骤,不能再进行拆分。 为了避免并发和竞争访问,在编写驱动程序时,我们应该尽早考虑并发和竞争问题,而不是在编写完驱动程序后再处理这些问题。这样可以避免在调试过程中出现难以查找的问题,并提高驱动程序的可靠性。 在Linux驱动程序中,可以使用方法来处理多线程问题。其中一常见的方法是使用互斥锁(mutex)来保护临界区,只允许一个线程访问临界区,其他线程需要等待。另一方法是使用信号量(semaphore),允许多个线程同时访问临界区,但是需要控制访问的数量。还可以使用读写锁(rwlock)来实现对临界区的读写操作的并发访问控制。 在编写驱动程序时,我们还应该注意避免死锁的问题。死锁是指多个线程互相等待对方释放资源导致无法继续执行的情况。为了避免死锁,我们需要合理地设计锁的获取和释放顺序,并避免循环依赖。 总结起来,Linux驱动中的多线程是为了实现并发访问和提高系统性能。在编写驱动程序时,我们需要考虑并发和竞争访问问题,并使用适当的同步机制来保护临界区,避免死锁的发生[3]。 引用自:一, linux系统并发产生的原因和竞争问题. (n.d.). Retrieved from https://www.jianshu.com/p/05f3b8f41e2f 引用自:二, linux系统竞态问题描述. (n.d.). Retrieved from https://www.jianshu.com/p/eb1f9f0b5f16

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值