linux多线程同步(Linux C 怎么实现两个线程同步读取两个内存的数据)

:暂无数据 2026-07-13 02:00:02 :0

linux多线程同步(Linux C 怎么实现两个线程同步读取两个内存的数据)

本篇文章给大家谈谈linux多线程同步,以及Linux C 怎么实现两个线程同步读取两个内存的数据对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录

Linux C 怎么实现两个线程同步读取两个内存的数据

在Linux系统中使用C/C++进行多线程编程时,我们遇到最多的就是对同一变量的多线程读写问题,大多情况下遇到这类问题都是通过锁机制来处理,但这对程序的性能带来了很大的影响,当然对于那些系统原生支持原子操作的数据类型来说,我们可以使用原子操作来处理,这能对程序的性能会得到一定的提高。那么对于那些系统不支持原子操作的自定义数据类型,在不使用锁的情况下如何做到线程安全呢?本文将从线程局部存储方面,简单讲解处理这一类线程安全问题的方法。

一、数据类型
在C/C++程序中常存在全局变量、函数内定义的静态变量以及局部变量,对于局部变量来说,其不存在线程安全问题,因此不在本文讨论的范围之内。全局变量和函数内定义的静态变量,是同一进程中各个线程都可以访问的共享变量,因此它们存在多线程读写问题。在一个线程中修改了变量中的内容,其他线程都能感知并且能读取已更改过的内容,这对数据交换来说是非常快捷的,但是由于多线程的存在,对于同一个变量可能存在两个或两个以上的线程同时修改变量所在的内存内容,同时又存在多个线程在变量在修改的时去读取该内存值,如果没有使用相应的同步机制来保护该内存的话,那么所读取到的数据将是不可预知的,甚至可能导致程序崩溃。
如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量,这就需要新的机制来实现,我们称之为Static memory local to a thread (线程局部静态变量),同时也可称之为线程特有数据(TSD: Thread-Specific Data)或者线程局部存储(TLS: Thread-Local Storage)。这一类型的数据,在程序中每个线程都会分别维护一份变量的副本(copy),并且长期存在于该线程中,对此类变量的操作不影响其他线程。如下图:

二、一次性初始化
在讲解线程特有数据之前,先让我们来了解一下一次性初始化。多线程程序有时有这样的需求:不管创建多少个线程,有些数据的初始化只能发生一次。列如:在C++程序中某个类在整个进程的生命周期内只能存在一个实例对象,在多线程的情况下,为了能让该对象能够安全的初始化,一次性初始化机制就显得尤为重要了。——在设计模式中这种实现常常被称之为单例模式(Singleton)。Linux中提供了如下函数来实现一次性初始化:
#include 《pthread.h》

// Returns 0 on success, or a positive error number on error
int pthread_once (pthread_once_t *once_control, void (*init) (void));
利用参数once_control的状态,函数pthread_once()可以确保无论有多少个线程调用多少次该函数,也只会执行一次由init所指向的由调用者定义的函数。init所指向的函数没有任何参数,形式如下:
void init (void)
{
// some variables initializtion in here
}
另外,参数once_control必须是pthread_once_t类型变量的指针,指向初始化为PTHRAD_ONCE_INIT的静态变量。在C++0x以后提供了类似功能的函数std::call_once (),用法与该函数类似。使用实例请参考

求助,关于linux的线程同步问题

【Linux多线程】三个经典同步问题
标签: 多线程同步生产者与消费者写者与读者

目录(?)[+]
在了解了《同步与互斥的区别 》之后,我们来看看几个经典的线程同步的例子。相信通过具体场景可以让我们学会分析和解决这类线程同步的问题,以便以后应用在实际的项目中。
一、生产者-消费者问题
问题描述:
一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
分析:
关系分析:生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。
整理思路:这里比较简单,只有生产者和消费者两个进程,且这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步的PV操作的位置。
信号量设置:信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,初值为1;信号量full用于记录当前缓冲池中“满”缓冲区数,初值为 0;信号量empty用于记录当前缓冲池中“空”缓冲区数,初值为n。
代码示例:(semaphore类的封装见下文)
#include《iostream》
#include《unistd.h》 // sleep
#include《pthread.h》
#include“semaphore.h“
using namespace std;
#define N 5

semaphore mutex(“/“, 1); // 临界区互斥信号量
semaphore empty(“/home“, N); // 记录空缓冲区数,初值为N
semaphore full(“/home/songlee“,0); // 记录满缓冲区数,初值为0
int buffer[N]; // 缓冲区,大小为N
int i=0;
int j=0;

void* producer(void* arg)
{
empty.P(); // empty减1
mutex.P();

buffer[i] = 10 + rand() % 90;
printf(“Producer %d write Buffer[%d]: %d\n“,arg,i+1,buffer[i]);
i = (i+1) % N;

mutex.V();
full.V(); // full加1
}

void* consumer(void* arg)
{
full.P(); // full减1
mutex.P();

printf(“ \033[1;31m“);
printf(“Consumer %d read Buffer[%d]: %d\n“,arg,j+1,buffer[j]);
printf(“\033[0m“);
j = (j+1) % N;

mutex.V();
empty.V(); // empty加1
}

int main()
{
pthread_t id;

// 开10个生产者线程,10个消费者线程
for(int k=0; k《10; ++k)
pthread_create(&id[k], NULL, producer, (void*)(k+1));

for(int k=0; k《10; ++k)
pthread_create(&id[k], NULL, consumer, (void*)(k+1));

sleep(1);
return 0;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455561234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556

编译运行输出结果:
Producer 1 write Buffer: 83
Producer 2 write Buffer: 26
Producer 3 write Buffer: 37
Producer 5 write Buffer: 35
Producer 4 write Buffer: 33
Consumer 1 read Buffer: 83
Producer 6 write Buffer: 35
Consumer 2 read Buffer: 26
Consumer 3 read Buffer: 37
Consumer 4 read Buffer: 35
Consumer 5 read Buffer: 33
Consumer 6 read Buffer: 35
Producer 7 write Buffer: 56
Producer 8 write Buffer: 22
Producer 10 write Buffer: 79
Consumer 9 read Buffer: 56
Consumer 10 read Buffer: 22
Producer 9 write Buffer: 11
Consumer 7 read Buffer: 79
Consumer 8 read Buffer: 1112345678910111213141516171819201234567891011121314151617181920

二、读者-写者问题
问题描述:
有读者和写者两组并发线程,共享一个文件,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
允许多个读者可以同时对文件执行读操作;
只允许一个写者往文件中写信息;
任一写者在完成写操作之前不允许其他读者或写者工作;
写者执行写操作前,应让已有的读者和写者全部退出。
分析:
关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV 操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。所以,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时,不同的读者对计数器的访问也应该是互斥的。
信号量设置:首先设置一个计数器count,用来记录当前的读者数量,初值为0;设置互斥信号量mutex,用于保护更新 count 变量时的互斥;设置互斥信号量rw用于保证读者和写者的互斥访问。
代码示例:
#include《iostream》
#include《unistd.h》 // sleep
#include《pthread.h》
#include“semaphore.h“
using namespace std;

int count = 0; // 记录当前的读者数量
semaphore mutex(“/“,1); // 用于保护更新count变量时的互斥
semaphore rw(“/home“,1); // 用于保证读者和写者的互斥

void* writer(void* arg)
{
rw.P(); // 互斥访问共享文件

printf(“ Writer %d start writing...\n“, arg);
sleep(1);
printf(“ Writer %d finish writing...\n“, arg);

rw.V(); // 释放共享文件
}

void* reader(void* arg)
{
mutex.P(); // 互斥访问count变量
if(count == 0) // 当第一个读线程读文件时
rw.P(); // 阻止写线程写
++count; // 读者计数器加1
mutex.V(); // 释放count变量

printf(“Reader %d start reading...\n“, arg);
sleep(1);
printf(“Reader %d finish reading...\n“, arg);

mutex.P(); // 互斥访问count变量
--count; // 读者计数器减1
if(count == 0) // 当最后一个读线程读完文件
rw.V(); // 允许写线程写
mutex.V(); // 释放count变量
}

int main()
{
pthread_t id; // 开6个读线程,2个写线程

pthread_create(&id, NULL, reader, (void*)1);
pthread_create(&id, NULL, reader, (void*)2);

pthread_create(&id, NULL, writer, (void*)1);
pthread_create(&id, NULL, writer, (void*)2);

pthread_create(&id, NULL, reader, (void*)3);
pthread_create(&id, NULL ,reader, (void*)4);
sleep(2);
pthread_create(&id, NULL, reader, (void*)5);
pthread_create(&id, NULL ,reader, (void*)6);

sleep(4);
return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

编译运行的结果如下:
Reader 2 start reading...
Reader 1 start reading...
Reader 3 start reading...
Reader 4 start reading...
Reader 1 finish reading...
Reader 2 finish reading...
Reader 3 finish reading...
Reader 4 finish reading...
Writer 1 start writing...
Writer 1 finish writing...
Writer 2 start writing...
Writer 2 finish writing...
Reader 5 start reading...
Reader 6 start reading...
Reader 5 finish reading...
Reader 6 finish reading...1234567891011121314151612345678910111213141516

三、哲学家进餐问题
问题描述:
一张圆桌上坐着 5 名哲学家,桌子上每两个哲学家之间摆了一根筷子,桌子的中间是一碗米饭,如图所示:

哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、右两根筷子(一根一根拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
分析:
关系分析:5名哲学家与左右邻居对其中间筷子的访问是互斥关系。
整理思路:显然这里有 5 个线程,那么要如何让一个哲学家拿到左右两个筷子而不造成死锁或饥饿现象?解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或死锁现象的发生。
信号量设置:定义互斥信号量数组chopstick = {1,1,1,1,1}用于对 5 根筷子的互斥访问。
示例代码:

linux系统中线程同步实现机制有哪些

LinuxThread的线程机制

LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr) 负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的“一对一“线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的 管理在核外函数库中实现。

1.线程描述数据结构及实现限制

LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量 __pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全 局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用 __pthread_main_thread表征__pthread_manager_thread的父线程(初始为 __pthread_initial_thread)。

struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它 一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、 p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列 pthread_create()之后形成的__pthread_handles数组将如下图所示:

图2 __pthread_handles数组结构

新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。

LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在 LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中 在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:

每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义 PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024 (PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字节)。

2.管理线程

“一对一“模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads 中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建 (__clone())并启动管理线程。

在一个进程空间内,管理线程与其他线程之间通过一对“管理管道(manager_pipe)“来通讯,该管道在创建管理线程之前创建,在成功启动 了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和 __pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求, 但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe)是作为__clone ()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。

创建管理线程的流程如下所示:
(全局变量pthread_manager_request初值为-1)

图3 创建管理线程的流程

初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id, 2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的 子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子 线程的概念应该当作子进程来理解。)

__pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为 timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出 整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。

然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。

3.线程栈

在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。

用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属 性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同 时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK 方式和用户自定义方式。

在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如 果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图:

图4 栈结构示意

低地址被保护的页面用来监测栈溢出。

对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。

不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。

4.线程id和进程id

每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。

如何实现linux下多线程之间的互斥与同步

Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。

Linux内核是多进程、多线程的操作系统,它提供了相当完整的内核同步方法。内核同步方法列表如下:
中断屏蔽
原子操作
自旋锁
读写自旋锁
顺序锁
信号量
读写信号量
BKL(大内核锁)
Seq锁
一、并发与竞态:
定义:
并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。
在linux中,主要的竞态发生在如下几种情况:
1、对称多处理器(SMP)多个CPU
特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。
2、单CPU内进程与抢占它的进程
3、中断(硬中断、软中断、Tasklet、底半部)与进程之间
只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生。
如果中断处理程序访问进程正在访问的资源,则竞态也会会发生。
多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源的时候,其他的执行单元都被禁止访问。

访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。

临界区和竞争条件:
所谓临界区(critical regions)就是访问和操作共享数据的代码段,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样,如果两个执行线程有可能处于同一个临界区中,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions),避免并发和防止竞争条件被称为同步。

死锁:
死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了,所有线程都在相互等待,但它们永远不会释放已经占有的资源,于是任何线程都无法继续,这便意味着死锁的发生。

二、中断屏蔽
在单CPU范围内避免竞态的一种简单方法是在进入临界区之前屏蔽系统的中断。
由于linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。
中断屏蔽的使用方法:
local_irq_disable()//屏蔽中断
//临界区
local_irq_enable()//开中断
特点:
由于linux系统的异步IO,进程调度等很多重要操作都依赖于中断,在屏蔽中断期间所有的中断都无法得到处理,因此长时间的屏蔽是很危险的,有可能造成数据丢失甚至系统崩溃,这就要求在屏蔽中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。
中断屏蔽只能禁止本CPU内的中断,因此,并不能解决多CPU引发的竞态,所以单独使用中断屏蔽并不是一个值得推荐的避免竞态的方法,它一般和自旋锁配合使用。

三、原子操作
定义:原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
(原子原本指的是不可分割的微粒,所以原子操作也就是不能够被分割的指令)
(它保证指令以“原子”的方式执行而不能被打断)
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是“ 原子操作“,因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。但是,在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以decl (递减指令)为例,这是一个典型的“读-改-写“过程,涉及两次内存访问。
通俗理解:
原子操作,顾名思义,就是说像原子一样不可再细分。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被OS的其他行为打断,是一个整体的过程,在其执行过程中,OS的其它行为是插不进来的。
分类:linux内核提供了一系列函数来实现内核中的原子操作,分为整型原子操作和位原子操作,共同点是:在任何情况下操作都是原子的,内核代码可以安全的调用它们而不被打断。

原子整数操作:
针对整数的原子操作只能对atomic_t类型的数据进行处理,在这里之所以引入了一个特殊的数据类型,而没有直接使用C语言的int型,主要是出于两个原因:
第一、让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用,同时,这也确保了该类型的数据不会被传递给其它任何非原子函数;
第二、使用atomic_t类型确保编译器不对相应的值进行访问优化——这点使得原子操作最终接收到正确的内存地址,而不是一个别名,最后就是在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。
原子整数操作最常见的用途就是实现计数器。
另一点需要说明原子操作只能保证操作是原子的,要么完成,要么不完成,不会有操作一半的可能,但原子操作并不能保证操作的顺序性,即它不能保证两个操作是按某个顺序完成的。如果要保证原子操作的顺序性,请使用内存屏障指令。
atomic_t和ATOMIC_INIT(i)定义
typedef struct { volatile int counter; } atomic_t;
#define ATOMIC_INIT(i) { (i) }

在你编写代码的时候,能使用原子操作的时候,就尽量不要使用复杂的加锁机制,对多数体系结构来讲,原子操作与更复杂的同步方法相比较,给系统带来的开销小,对高速缓存行的影响也小,但是,对于那些有高性能要求的代码,对多种同步方法进行测试比较,不失为一种明智的作法。

原子位操作:
针对位这一级数据进行操作的函数,是对普通的内存地址进行操作的。它的参数是一个指针和一个位号。

为方便其间,内核还提供了一组与上述操作对应的非原子位函数,非原子位函数与原子位函数的操作完全相同,但是,前者不保证原子性,且其名字前缀多两个下划线。例如,与test_bit()对应的非原子形式是_test_bit(),如果你不需要原子性操作(比如,如果你已经用锁保护了自己的数据),那么这些非原子的位函数相比原子的位函数可能会执行得更快些。

四、自旋锁
自旋锁的引入:
如 果每个临界区都能像增加变量这样简单就好了,可惜现实不是这样,而是临界区可以跨越多个函数,例如:先得从一个数据结果中移出数据,对其进行格式转换和解 析,最后再把它加入到另一个数据结构中,整个执行过程必须是原子的,在数据被更新完毕之前,不能有其他代码读取这些数据,显然,简单的原子操作是无能为力 的(在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是“ 原子操作“,因为中断只能发生于指令之间),这就需要使用更为复杂的同步方法——锁来提供保护。

自旋锁的介绍:
Linux内核中最常见的锁是自旋锁(spin lock),自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图获得一个被争用(已经被持有)的自旋锁,那么该线程就会一直进行忙循环—旋转—等待锁重新可用,要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行,在任意时间,自旋锁都可以防止多于一个的执行线程同时进入理解区,注意同一个锁可以用在多个位置—例如,对于给定数据的所有访问都可以得到保护和同步。
一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),所以自旋锁不应该被长时间持有,事实上,这点正是使用自旋锁的初衷,在短期间内进行轻量级加锁,还可以采取另外的方式来处理对锁的争用:让请求线程睡眠,直到锁重新可用时再唤醒它,这样处理器就不必循环等待,可以去执行其他代码,这也会带来一定的开销——这里有两次明显的上下文切换, 被阻塞的线程要换出和换入。因此,持有自旋锁的时间最好小于完成两次上下文切换的耗时,当然我们大多数人不会无聊到去测量上下文切换的耗时,所以我们让持 有自旋锁的时间应尽可能的短就可以了,信号量可以提供上述第二种机制,它使得在发生争用时,等待的线程能投入睡眠,而不是旋转。
自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它们会导致睡眠),在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在 当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经持有的自旋锁,这样以来,中断处理程序就会自旋, 等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行,这正是我们在前一章节中提到的双重请求死锁,注意,需要关闭的只是当前处理器上的中断,如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。

自旋锁的简单理解:
理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前正在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用”。如果A执行单元首先进入例程,它将持有自旋锁,当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入。

自旋锁的API函数:

其实介绍的几种信号量和互斥机制,其底层源码都是使用自旋锁,可以理解为自旋锁的再包装。所以从这里就可以理解为什么自旋锁通常可以提供比信号量更高的性能。
自旋锁是一个互斥设备,他只能会两个值:“锁定”和“解锁”。它通常实现为某个整数之中的单个位。
“测试并设置”的操作必须以原子方式完成。
任何时候,只要内核代码拥有自旋锁,在相关CPU上的抢占就会被禁止。
适用于自旋锁的核心规则:
(1)任何拥有自旋锁的代码都必须使原子的,除服务中断外(某些情况下也不能放弃CPU,如中断服务也要获得自旋锁。为了避免这种锁陷阱,需要在拥有自旋锁时禁止中断),不能放弃CPU(如休眠,休眠可发生在许多无法预期的地方)。否则CPU将有可能永远自旋下去(死机)。
(2)拥有自旋锁的时间越短越好。

需 要强调的是,自旋锁别设计用于多处理器的同步机制,对于单处理器(对于单处理器并且不可抢占的内核来说,自旋锁什么也不作),内核在编译时不会引入自旋锁 机制,对于可抢占的内核,它仅仅被用于设置内核的抢占机制是否开启的一个开关,也就是说加锁和解锁实际变成了禁止或开启内核抢占功能。如果内核不支持抢 占,那么自旋锁根本就不会编译到内核中。
内核中使用spinlock_t类型来表示自旋锁,它定义在:
typedef struct {
raw_spinlock_t raw_lock;
#if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
unsigned int break_lock;
#endif
} spinlock_t;

对于不支持SMP的内核来说,struct raw_spinlock_t什么也没有,是一个空结构。对于支持多处理器的内核来说,struct raw_spinlock_t定义为
typedef struct {
unsigned int slock;
} raw_spinlock_t;

slock表示了自旋锁的状态,“1”表示自旋锁处于解锁状态(UNLOCK),“0”表示自旋锁处于上锁状态(LOCKED)。
break_lock表示当前是否由进程在等待自旋锁,显然,它只有在支持抢占的SMP内核上才起作用。
自旋锁的实现是一个复杂的过程,说它复杂不是因为需要多少代码或逻辑来实现它,其实它的实现代码很少。自旋锁的实现跟体系结构关系密切,核心代码基本也是由汇编语言写成,与体协结构相关的核心代码都放在相关的目录下,比如。对于我们驱动程序开发人员来说,我们没有必要了解这么spinlock的内部细节,如果你对它感兴趣,请参考阅读Linux内核源代码。对于我们驱动的spinlock接口,我们只需包括头文件。在我们详细的介绍spinlock的API之前,我们先来看看自旋锁的一个基本使用格式:
#include
spinlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock(&lock);
....
spin_unlock(&lock);

从使用上来说,spinlock的API还很简单的,一般我们会用的的API如下表,其实它们都是定义在中的宏接口,真正的实现在中
#include
SPIN_LOCK_UNLOCKED
DEFINE_SPINLOCK
spin_lock_init( spinlock_t *)
spin_lock(spinlock_t *)
spin_unlock(spinlock_t *)
spin_lock_irq(spinlock_t *)
spin_unlock_irq(spinlock_t *)
spin_lock_irqsace(spinlock_t *,unsigned long flags)
spin_unlock_irqsace(spinlock_t *, unsigned long flags)
spin_trylock(spinlock_t *)
spin_is_locked(spinlock_t *)

• 初始化
spinlock有两种初始化形式,一种是静态初始化,一种是动态初始化。对于静态的spinlock对象,我们用 SPIN_LOCK_UNLOCKED来初始化,它是一个宏。当然,我们也可以把声明spinlock和初始化它放在一起做,这就是 DEFINE_SPINLOCK宏的工作,因此,下面的两行代码是等价的。
DEFINE_SPINLOCK (lock);
spinlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock_init 函数一般用来初始化动态创建的spinlock_t对象,它的参数是一个指向spinlock_t对象的指针。当然,它也可以初始化一个静态的没有初始化的spinlock_t对象。
spinlock_t *lock
......
spin_lock_init(lock);

• 获取锁
内核提供了三个函数用于获取一个自旋锁。
spin_lock:获取指定的自旋锁。
spin_lock_irq:禁止本地中断并获取自旋锁。
spin_lock_irqsace:保存本地中断状态,禁止本地中断并获取自旋锁,返回本地中断状态。

自旋锁是可以使用在中断处理程序中的,这时需要使用具有关闭本地中断功能的函数,我们推荐使用 spin_lock_irqsave,因为它会保存加锁前的中断标志,这样就会正确恢复解锁时的中断标志。如果spin_lock_irq在加锁时中断是关闭的,那么在解锁时就会错误的开启中断。

另外两个同自旋锁获取相关的函数是:
spin_trylock():尝试获取自旋锁,如果获取失败则立即返回非0值,否则返回0。
spin_is_locked():判断指定的自旋锁是否已经被获取了。如果是则返回非0,否则,返回0。
• 释放锁
同获取锁相对应,内核提供了三个相对的函数来释放自旋锁。
spin_unlock:释放指定的自旋锁。
spin_unlock_irq:释放自旋锁并激活本地中断。
spin_unlock_irqsave:释放自旋锁,并恢复保存的本地中断状态。

五、读写自旋锁
如 果临界区保护的数据是可读可写的,那么只要没有写操作,对于读是可以支持并发操作的。对于这种只要求写操作是互斥的需求,如果还是使用自旋锁显然是无法满 足这个要求(对于读操作实在是太浪费了)。为此内核提供了另一种锁-读写自旋锁,读自旋锁也叫共享自旋锁,写自旋锁也叫排他自旋锁。
读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元,当然,读和写也不能同时进行。
读写自旋锁的使用也普通自旋锁的使用很类似,首先要初始化读写自旋锁对象:
// 静态初始化
rwlock_t rwlock = RW_LOCK_UNLOCKED;
//动态初始化
rwlock_t *rwlock;
...
rw_lock_init(rwlock);

在读操作代码里对共享数据获取读自旋锁:
read_lock(&rwlock);
...
read_unlock(&rwlock);

在写操作代码里为共享数据获取写自旋锁:
write_lock(&rwlock);
...
write_unlock(&rwlock);

需要注意的是,如果有大量的写操作,会使写操作自旋在写自旋锁上而处于写饥饿状态(等待读自旋锁的全部释放),因为读自旋锁会自由的获取读自旋锁。

读写自旋锁的函数类似于普通自旋锁,这里就不一一介绍了,我们把它列在下面的表中。
RW_LOCK_UNLOCKED
rw_lock_init(rwlock_t *)
read_lock(rwlock_t *)
read_unlock(rwlock_t *)
read_lock_irq(rwlock_t *)
read_unlock_irq(rwlock_t *)
read_lock_irqsave(rwlock_t *, unsigned long)
read_unlock_irqsave(rwlock_t *, unsigned long)
write_lock(rwlock_t *)
write_unlock(rwlock_t *)
write_lock_irq(rwlock_t *)
write_unlock_irq(rwlock_t *)
write_lock_irqsave(rwlock_t *, unsigned long)
write_unlock_irqsave(rwlock_t *, unsigned long)
rw_is_locked(rwlock_t *)
六、顺序琐
顺序琐(seqlock)是对读写锁的一种优化,若使用顺序琐,读执行单元绝不会被写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序琐保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。
但是,写执行单元与写执行单元之间仍然是互斥的,即如果有写执行单元在进行写操作,其它写执行单元必须自旋在哪里,直到写执行单元释放了顺序琐。
如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据,以便确保得到的数据是完整的,这种锁在读写同时进行的概率比较小时,性能是非常好的,而且它允许读写同时进行,因而更大的提高了并发性,
注意,顺序琐由一个限制,就是它必须被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致Oops。
七、信号量
Linux中的信号量是一种睡眠锁,如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠,这时处理器能重获自由,从而去执行其它代码,当持有信号量的进程将信号量释放后,处于等待队列中的哪个任务被唤醒,并获得该信号量。
信号量,或旗标,就是我们在操作系统里学习的经典的P/V原语操作。
P:如果信号量值大于0,则递减信号量的值,程序继续执行,否则,睡眠等待信号量大于0。
V:递增信号量的值,如果递增的信号量的值大于0,则唤醒等待的进程。

信号量的值确定了同时可以有多少个进程可以同时进入临界区,如果信号量的初始值始1,这信号量就是互斥信号量(MUTEX)。对于大于1的非0值信号量,也可称为计数信号量(counting semaphore)。对于一般的驱动程序使用的信号量都是互斥信号量。
类似于自旋锁,信号量的实现也与体系结构密切相关,具体的实现定义在头文件中,对于x86_32系统来说,它的定义如下:
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};

信号量的初始值count是atomic_t类型的,这是一个原子操作类型,它也是一个内核同步技术,可见信号量是基于原子操作的。我们会在后面原子操作部分对原子操作做详细介绍。

信号量的使用类似于自旋锁,包括创建、获取和释放。我们还是来先展示信号量的基本使用形式:
static DECLARE_MUTEX(my_sem);
......
if (down_interruptible(&my_sem))

{
return -ERESTARTSYS;
}
......
up(&my_sem)

Linux内核中的信号量函数接口如下:
static DECLARE_SEMAPHORE_GENERIC(name, count);
static DECLARE_MUTEX(name);
seam_init(struct semaphore *, int);
init_MUTEX(struct semaphore *);
init_MUTEX_LOCKED(struct semaphore *)
down_interruptible(struct semaphore *);
down(struct semaphore *)
down_trylock(struct semaphore *)
up(struct semaphore *)
• 初始化信号量
信号量的初始化包括静态初始化和动态初始化。静态初始化用于静态的声明并初始化信号量。
static DECLARE_SEMAPHORE_GENERIC(name, count);
static DECLARE_MUTEX(name);

对于动态声明或创建的信号量,可以使用如下函数进行初始化:
seam_init(sem, count);
init_MUTEX(sem);
init_MUTEX_LOCKED(struct semaphore *)

显然,带有MUTEX的函数始初始化互斥信号量。LOCKED则初始化信号量为锁状态。
• 使用信号量
信号量初始化完成后我们就可以使用它了
down_interruptible(struct semaphore *);
down(struct semaphore *)
down_trylock(struct semaphore *)
up(struct semaphore *)

down函数会尝试获取指定的信号量,如果信号量已经被使用了,则进程进入不可中断的睡眠状态。down_interruptible则会使进程进入可中断的睡眠状态。关于进程状态的详细细节,我们在内核的进程管理里在做详细介绍。

down_trylock尝试获取信号量, 如果获取成功则返回0,失败则会立即返回非0。

当退出临界区时使用up函数释放信号量,如果信号量上的睡眠队列不为空,则唤醒其中一个等待进程。

八、读写信号量
类似于自旋锁,信号量也有读写信号量。读写信号量API定义在头文件中,它的定义其实也是体系结构相关的,因此具体实现定义在头文件中,以下是x86的例子:
struct rw_semaphore {
signed long count;
spinlock_t wait_lock;
struct list_head wait_list;
};

Linux多线程同步之消息队列有何特点

消息队列是消息的链表,存放在内核中并有消息队列标示符标示。
msgget用于创建一个新队列或打开一个现存的队列。msgsnd将新消息加入到消息队列中;每个
消息包括一个long型的type;和消息缓存;msgrcv用于从队列中取出消息;取消息很智能,不一定先进先出
①msgget,创建一个新队列或打开一个现有队列
#include
int msgget ( key_t key, int flag );
//成功返回消息队列ID;错误返回-1
②msgsnd: 发送消息
#include
int msgsnd( int msgid, const void* ptr, size_t nbytes, int flag )
//成功返回0,错误返回-1
a:
flag可以指定为IPC_NOWAIT;
若消息队列已满,则msgsnd立即出错返回EABAIN;
若没指定IPC_NOWAIT; msgsnd会阻塞,直到消息队列有空间为止
③msgrcv: 读取消息:
ssize_t msgrcv( int msgid, void* ptr, size_t nbytes, long type, int flag );
a. type == 0; 返回消息队列中第一个消息,先进先出
b. type 》 0
返回消息队列中类型为tpye的第一个消息
c. type 《 0
返回消息队列中类型 《=
|type| 的数据;若这种消息有若干个,则取类型值最小的消息
消息队列创建步骤:
#define
MSG_FILE “.“
struct msgtype {
long mtype;
char buffer[BUFFER+1];
};
if((key=ftok(MSG_FILE,’a’))==-1)
{
fprintf(stderr,“Creat Key Error:%s\n“, strerror(errno));
exit
(1);
}
if((msgid=msgget(key, IPC_CREAT | 0666/*PERM*/))==-1)
{
fprintf(stderr,“Creat Message
Error:%s\n“, strerror(errno));
exit
(1);
}

linux C++多线程怎么实现同步

如果想多线程并发,最好让这些线程对数据的访问不发生读写冲突,就不用加锁。
假设你是处理多个文件夹,你可以考虑用自己的数据结构做文件夹映射,为每一个线程定义自己的文件夹列表。在加载文件夹列表时,将文件夹均配到线程的任务列表中,每个线程都有自己指定的文件夹处理,相互之间就不用加锁。

linux 多进程信号同步问题

朋友你好:希望能帮到你。互相学习。
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。
1)互斥锁(mutex)
通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_destroy(pthread_mutex *mutex);
int pthread_mutex_unlock(pthread_mutex *
(1)先初始化锁init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER
attr_t有:
PTHREAD_MUTEX_TIMED_NP:其余线程等待队列
PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争
PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;
PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争
(2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY
(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁
(4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用内存资源
示例代码
#include 《cstdio》
#include 《cstdlib》
#include 《unistd.h》
#include 《pthread.h》
#include “iostream“
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int tmp;
void* thread(void *arg)
{
cout 《《 “thread id is “ 《《 pthread_self() 《《 endl;
pthread_mutex_lock(&mutex);
tmp = 12;
cout 《《 “Now a is “ 《《 tmp 《《 endl;
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t id;
cout 《《 “main thread id is “ 《《 pthread_self() 《《 endl;
tmp = 3;

cout 《《 “In main func tmp = “ 《《 tmp 《《 endl;
if (!pthread_create(&id, NULL, thread, NULL))
{
cout 《《 “Create thread success!“ 《《 endl;
}
else
{
cout 《《 “Create thread failed!“ 《《 endl;
}
pthread_join(id, NULL);
pthread_mutex_destroy(&mutex);

return 0;
}
编译: g++ -o thread testthread.cpp -lpthread
说明:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在使用pthread_create()创建线程,以及调用pthread_atfork()函数建立fork处理程序时,需要链接该库。在编译中要加 -lpthread参数。
2)条件变量(cond)
利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER(前者为动态初始化,后者为静态初始化);属性置为NULL
(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真,timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)清除条件变量:destroy;无线程等待,否则返回EBUSY
对于
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
一定要在mutex的锁定区域内使用。
如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考
pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!
另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。
说明:
(1)pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。
(2)互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。
(3)pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁,且pthread_cond_timedwait返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970年1月1日00:00:00 GMT。
(4)pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。
(5)条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或pthread_cond_boardcast 函数,可能导致调用线程死锁。
示例程序1
#include 《stdio.h》

#include 《pthread.h》

#include “stdlib.h“

#include “unistd.h“
pthread_mutex_t mutex;

pthread_cond_t cond;
void hander(void *arg)

{

free(arg);

(void)pthread_mutex_unlock(&mutex);

}
void *thread1(void *arg)

{

pthread_cleanup_push(hander, &mutex);

while(1)

{

printf(“thread1 is running\n“);

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond,&mutex);

printf(“thread1 applied the condition\n“);

pthread_mutex_unlock(&mutex);

sleep(4);

}

pthread_cleanup_pop(0);

}
void *thread2(void *arg)

{

while(1)

{

printf(“thread2 is running\n“);

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond,&mutex);

printf(“thread2 applied the condition\n“);

pthread_mutex_unlock(&mutex);

sleep(1);

}

}
int main()

{

pthread_t thid1,thid2;

printf(“condition variable study!\n“);

pthread_mutex_init(&mutex,NULL);

pthread_cond_init(&cond,NULL);

pthread_create(&thid1,NULL,thread1,NULL);

pthread_create(&thid2,NULL,thread2,NULL);

sleep(1);

do

{

pthread_cond_signal(&cond);

}while(1);

sleep(20);

pthread_exit(0);

return 0;

}
示例程序2:
#include 《pthread.h》

#include 《unistd.h》

#include “stdio.h“

#include “stdlib.h“
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node

{

int n_number;

struct node *n_next;

} *head = NULL;
/*[thread_func]*/

static void cleanup_handler(void *arg)

{

printf(“Cleanup handler of second thread./n“);

free(arg);

(void)pthread_mutex_unlock(&mtx);

}
static void *thread_func(void *arg)

{

struct node *p = NULL;

pthread_cleanup_push(cleanup_handler, p);

while (1)

{

//这个mutex主要是用来保证pthread_cond_wait的并发性

pthread_mutex_lock(&mtx);

while (head == NULL)

{

//这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何

//这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线

//程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。

//这个时候,应该让线程继续进入pthread_cond_wait

// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,

//然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立

//而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源

//用这个流程是比较清楚的/*block--》unlock--》wait() return--》lock*/

pthread_cond_wait(&cond, &mtx);

p = head;

head = head-》n_next;

printf(“Got %d from front of queue/n“, p-》n_number);

free(p);

}

pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁

}

pthread_cleanup_pop(0);

return 0;

}
int main(void)

{

pthread_t tid;

int i;

struct node *p;

//子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而

//不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大

pthread_create(&tid, NULL, thread_func, NULL);

sleep(1);

for (i = 0; i 《 10; i++)

{

p = (struct node*)malloc(sizeof(struct node));

p-》n_number = i;

pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,

p-》n_next = head;

head = p;

pthread_cond_signal(&cond);

pthread_mutex_unlock(&mtx); //解锁

sleep(1);

}

printf(“thread 1 wanna end the line.So cancel thread 2./n“);
//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出

//线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。

pthread_cancel(tid);

pthread_join(tid, NULL);

printf(“All done -- exiting/n“);

return 0;

}
3)信号量
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。
信号量函数的名字都以“sem_“打头。线程使用的基本信号量函数有四个。
#include 《semaphore.h》
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。
int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。
示例代码:
#include 《stdlib.h》

#include 《stdio.h》

#include 《unistd.h》

#include 《pthread.h》

#include 《semaphore.h》

#include 《errno.h》
#define return_if_fail(p) if((p) == 0){printf (“[%s]:func error!/n“, __func__);return;}
typedef struct _PrivInfo

{

sem_t s1;

sem_t s2;

time_t end_time;

}PrivInfo;

static void info_init (PrivInfo* thiz);
static void info_destroy (PrivInfo* thiz);

static void* pthread_func_1 (PrivInfo* thiz);

static void* pthread_func_2 (PrivInfo* thiz);
int main (int argc, char** argv)

{

pthread_t pt_1 = 0;

pthread_t pt_2 = 0;

int ret = 0;

PrivInfo* thiz = NULL;
thiz = (PrivInfo* )malloc (sizeof (PrivInfo));

if (thiz == NULL)

{

printf (“[%s]: Failed to malloc priv./n“);

return -1;

}
info_init (thiz);
ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);

if (ret != 0)

{

perror (“pthread_1_create:“);

}
ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);

if (ret != 0)

{

perror (“pthread_2_create:“);

}
pthread_join (pt_1, NULL);

pthread_join (pt_2, NULL);

info_destroy (thiz);
return 0;
}
static void info_init (PrivInfo* thiz)

{

return_if_fail (thiz != NULL);
thiz-》end_time = time(NULL) + 10;
sem_init (&thiz-》s1, 0, 1);

sem_init (&thiz-》s2, 0, 0);
return;

}
static void info_destroy (PrivInfo* thiz)

{

return_if_fail (thiz != NULL);
sem_destroy (&thiz-》s1);

sem_destroy (&thiz-》s2);
free (thiz);

thiz = NULL;
return;

}
static void* pthread_func_1 (PrivInfo* thiz)

{

return_if_fail (thiz != NULL);
while (time(NULL) 《 thiz-》end_time)

{

sem_wait (&thiz-》s2);

printf (“pthread1: pthread1 get the lock./n“);
sem_post (&thiz-》s1);

printf (“pthread1: pthread1 unlock/n“);
sleep (1);

}
return;

}
static void* pthread_func_2 (PrivInfo* thiz)

{

return_if_fail (thiz != NULL);
while (time (NULL) 《 thiz-》end_time)

{

sem_wait (&thiz-》s1);

printf (“pthread2: pthread2 get the unlock./n“);
sem_post (&thiz-》s2);

printf (“pthread2: pthread2 unlock./n“);
sleep (1);

}
return;

}
通 过执行结果后,可以看出,会先执行线程二的函数,然后再执行线程一的函数。它们两就实现了同步

文章分享结束,linux多线程同步和Linux C 怎么实现两个线程同步读取两个内存的数据的答案你都知道了吗?欢迎再次光临本站哦!

linux多线程同步(Linux C 怎么实现两个线程同步读取两个内存的数据)

本文编辑:admin

更多文章:


repercussions(都是余波,repercussions和aftermath有什么区别啊)

repercussions(都是余波,repercussions和aftermath有什么区别啊)

今天给各位分享都是余波,repercussions和aftermath有什么区别啊的知识,其中也会对都是余波,repercussions和aftermath有什么区别啊进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

2026年10月11日 09:00

dropdownlist 绑定(DropDownList 绑定所有项 并 显示指定项)

dropdownlist 绑定(DropDownList 绑定所有项 并 显示指定项)

本篇文章给大家谈谈dropdownlist 绑定,以及DropDownList 绑定所有项 并 显示指定项对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

2026年10月11日 08:50

协方差计算公式(协方差的计算公式)

协方差计算公式(协方差的计算公式)

这篇文章给大家聊聊关于协方差计算公式,以及协方差的计算公式对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。

2026年10月11日 08:10

易语言网页api接口怎么调用(易语言,怎么读取网页json的api)

易语言网页api接口怎么调用(易语言,怎么读取网页json的api)

本篇文章给大家谈谈易语言网页api接口怎么调用,以及易语言,怎么读取网页json的api对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。

2026年10月11日 08:00

majority of(the majority of 和 a majority of的区别以及用法例句)

majority of(the majority of 和 a majority of的区别以及用法例句)

这篇文章给大家聊聊关于majority of,以及the majority of 和 a majority of的区别以及用法例句对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。

2026年10月11日 07:40

汉字机内码查询表(1个汉字的机内码是几位谢谢)

汉字机内码查询表(1个汉字的机内码是几位谢谢)

大家好,关于汉字机内码查询表很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于1个汉字的机内码是几位谢谢的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!

2026年10月11日 07:20

promote翻译(英语翻译倡导怎么说)

promote翻译(英语翻译倡导怎么说)

其实promote翻译的问题并不复杂,但是又很多的朋友都不太了解英语翻译倡导怎么说,因此呢,今天小编就来为大家分享promote翻译的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!

2026年10月11日 06:30

用手机如何导航?开车用手机导航哪个软件最好

用手机如何导航?开车用手机导航哪个软件最好

今天给各位分享用手机如何导航的知识,其中也会对用手机如何导航进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

2026年10月11日 06:20

多线程技术有什么用(多线程有什么作用)

多线程技术有什么用(多线程有什么作用)

其实多线程技术有什么用的问题并不复杂,但是又很多的朋友都不太了解多线程有什么作用,因此呢,今天小编就来为大家分享多线程技术有什么用的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!

2026年10月11日 05:40

another time(another time和other time的区别)

another time(another time和other time的区别)

大家好,another time相信很多的网友都不是很明白,包括another time和other time的区别也是一样,不过没有关系,接下来就来为大家分享关于another time和another time和other time的区

2026年10月11日 05:00

最近更新

repercussions(都是余波,repercussions和aftermath有什么区别啊)
2026-10-11 09:00:05 浏览:0
dropdownlist 绑定(DropDownList 绑定所有项 并 显示指定项)
2026-10-11 08:50:04 浏览:0
majority of(the majority of 和 a majority of的区别以及用法例句)
2026-10-11 07:40:02 浏览:0
热门文章

标签列表