Posix在线文档:
本文主要参考这位大神的文章:
线程安全问题
多线程编程中,经常遇到的就是线程安全问题,或者说要考虑线程同步的问题。
线程同步这个概念其实挺抽象的。关于线程同步,是指各线程执行时需要按照一定的先后顺序,而不是各线程自己想干嘛就干嘛。
本人之前对线程同步,以及主要的同步技术如互斥锁、信号量、条件变量等没怎么搞透彻,这里再想一想看看。
考虑以下三个场景:
- 场景1:多个线程,改变了同一个全局变量;
- 场景2:多个线程,都调用printf来打印输出;
- 场景3:多个线程,其中A线程必须等B线程执行结束才能执行。
针对场景1,可以使用互斥锁,保证同一时间只有一个线程来操作全局变量,本质是什么呢?本质是因为修改变量的过程并不是原子操作,而是会被打断的,所以,需要在修改变量期间,保证不会被其他线程打断;
针对场景2,我们需要的其实也是printf这个过程不会被打断,要不然就是这个线程打印一会儿,那个线程打印一会儿,输出的内容都串在一起了,这种情况下怎么处理呢?在freertos中,不想某段操作被其他任务打断,可以使用临界区函数来实现,那么Linux中呢?其实可以用信号量,想一想,信号量其实主要就是统计某个资源的使用情况,printf其实就是一个输出的资源,我们可以用信号量来实现,每次只有一个线程可以获取该资源;
针对这两个场景再想想,printf是个资源,其实一个全局变量也是个资源,是不是也可以用信号量呢?另外,printf要想不被打断,是不是也可以使用互斥锁呢?理论上都是可以互换的。
二者本质上都是为了实现原子操作。
再来看看第三个场景,各线程有先后执行顺序,就可以使用条件变量,这一点,相对前面两个场景,没那么容易搞混。
而这三个技术,互斥锁、信号量以及条件变量,就是多线程编程中,实现线程同步的主要手段。再看看线程同步这个词,意思其实就是我这个线程操作资源时,你其他线程得等着,这就是同步的含义。使用线程同步技术,从而保证线程安全。
这几个技术的本质应该是类似的,只是侧重点不一样,需要在实际开发中慢慢体会。
互斥锁和信号量的区别?
参考:
信号量和互斥量(锁)的区别_信号量和互斥锁的区别-CSDN博客
为了更好地区分应用场景,再想想。
互斥锁主要是用在某个会被多线程调用的代码段中;
而信号量,是先有个信号量,然后这个线程释放信号量,另一个线程获取信号量,其实也可以控制线程实现的先后顺序。
根据《UNIX环境高级编程》,Pthread处理线程同步的,主要是互斥锁和条件变量,而信号量在进程间通信才被提及,貌似信号量主要是用于多进程对共享数据对象的访问。
注意Linux中的相关概念和RTOS中的相关概念的区别。
Linux中会有多个进程,相对复杂,RTOS中一般只有一个进程,也就是我们的应用,然后里面有各个子任务,对应的是Linux中的线程的概念。
有哪些不一样呢?比如RTOS中消息队列就是在各线程任务之间传递消息,而Linux中主要是用在进程间通信;又比如RTOS中的信号量分为好几种,比如二值信号量、计数信号量、互斥信号量等等,都用在任务间的同步,而Linux中,这些技术有的是用于线程的,有的是用于进程的;再比如RTOS中有临界区保护这一技术手段,不过Linux中一般就是通过互斥锁信号量等等实现。
总之要明确的是,RTOS中一般只有线程的概念,也就是各子任务,所以各技术,都是用在任务之间;但是Linux中,既有线程又有进程,不同的技术有不同的应用场景,所以要加以区分。 另外,不同的操作系统,可能略有差别,实际使用时,需要进一步了解,不过用法都大差不差,重要的是,别以为都是统一的用法,然后产生疑惑,还不知道是咋回事。
Pthreads 概述
历史上,硬件销售商实现了私有版本的多线程库。这些实现在本质上各自不同,使得程序员难于开发可移植的应用程序。 为了使用线程所提供的强大优点,需要一个标准的程序接口。对于UNIX系统,IEEE POSIX 1003.1c(1995)标准制订了这一标准接口。依赖于该标准的实现就称为POSIX threads 或者Pthreads。现在多数硬件销售商也提供Pthreads,附加于私有的API。 Pthreads 被定义为一些C语言类型和函数调用,用pthread.h头(包含)文件和线程库实现。
所以说,Pthreads,其实就是POSIX threads,也就是符合posix标准的线程操作接口。
线程管理
Pthreads API中的函数可以非正式的划分为三大类:
线程管理(Thread management): 第一类函数直接用于线程:创建(creating),分离(detaching),连接(joining)等等。包含了用于设置和查询线程属性(可连接,调度属性等)的函数。
互斥量(Mutexes): 第二类函数是用于线程同步的,称为互斥量(mutexes),是"mutual exclusion"的缩写。Mutex函数提供了创建,销毁,锁定和解锁互斥量的功能。同时还包括了一些用于设定或修改互斥量属性的函数。
条件变量(Condition variables):第三类函数处理共享一个互斥量的线程间的通信,基于程序员指定的条件。这类函数包括指定的条件变量的创建,销毁,等待和受信(signal)。设置查询条件变量属性的函数也包含其中。
创建和结束线程
函数:
pthread_create (thread,attr,start_routine,arg)
pthread_exit (status)
pthread_attr_init (attr)
pthread_attr_destroy (attr)
最初,main函数包含了一个缺省的线程,也就是主线程。其它线程则需要程序员显式地创建。
有个疑惑,主线程mian里面创建完其他线程后,是进入while死循环,还是直接return返回?
参考:
线程创建后,避免主线程先于子线程结束的四种方式_怎么让新创建的线程不随主线程结束运行-CSDN博客
pthread_create 创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
pthread_create参数:
- thread:返回一个新线程标识符,也就是线程ID
- attr:线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值,一般需要时会在属性里设置线程的优先级和栈大小;
- start_routine:线程将会执行的函数,注意该函数的类型void *(*)(void *)
- arg: 传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。
一个进程可以创建的线程最大数量取决于系统实现。
Q:一个线程被创建后,怎么知道操作系统何时调度该线程使之运行?
A:线程何时何地被执行取决于操作系统实现,强壮的程序应该不依赖于线程执行的顺序。
线程属性:
- 线程被创建时会带有默认的属性。其中的一些属性可以被程序员用线程属性对象来修改;
- pthread_attr_init 和 pthread_attr_destroy用于初始化/销毁先成属性对象;
结束终止:
- 结束线程的方法有以下几种:
- 线程从主线程(main函数的初始线程)返回;
- 线程调用了pthread_exit函数;
- 其它线程使用 pthread_cancel函数结束线程;
- 调用exec或者exit函数,整个进程结束;
pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作不再需要的时候被调用,退出线程。注意,哪个线程调用的,就结束哪个线程。
#include <pthread.h> void pthread_exit(void *value_ptr);
参数retval 是 void* 类型的指针,可以指向任何类型的数据,它指向的数据将作为线程退出时的返回值。如果线程不需要返回任何数据,将 retval 参数置为 NULL 即可。
示例如下:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> void *thread_fun1(void *param) { while(1) { printf("I am thread-1\n"); sleep(1); } return NULL; } void *thread_fun2(void *param) { while(1) { printf("you are thread-2\n"); sleep(1); } return NULL; } int main(int argc, char *argv[]) { pthread_t tid1, tid2; int rc; rc = pthread_create(&tid1, NULL, thread_fun1, NULL); if (rc < 0){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } rc = pthread_create(&tid2, NULL, thread_fun2, NULL); if (rc < 0){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } pthread_exit(NULL);//主线程退出了 }
互斥量
互斥量(Mutex)是“mutual exclusion”的缩写。互斥量是实现线程同步,和保护同时写共享数据的主要方法。
互斥量对共享数据的保护就像一把锁。在Pthreads中,任何时候仅有一个线程可以锁定互斥量,因此,当多个线程尝试去锁定该互斥量时仅有一个会成功。直到锁定互斥量的线程解锁互斥量后,其他线程才可以去锁定互斥量。线程必须轮着访问受保护数据。
互斥量可以防止“竞争”条件。
一个拥有互斥量的线程经常用于更新全局变量。确保了多个线程更新同样的变量以安全的方式运行,最终的结果和一个线程处理的结果是相同的(在受保护的地方,多个线程对于资源的操作,就像是一个线程中对数据的先后处理)。这个更新的变量属于一个“临界区(critical section)”。
使用互斥量的典型顺序如下:
- 创建和初始一个互斥量
- 多个线程尝试去锁定该互斥量
- 仅有一个线程可以成功锁定改互斥量
- 锁定成功的线程做一些处理
- 线程解锁该互斥量
- 另外一个线程获得互斥量,重复上述过程
- 最后销毁互斥量
当多个线程竞争同一个互斥量时,失败的线程会阻塞在lock调用处。可以用“trylock”替换“lock”,则失败时不会阻塞。
当保护共享数据时,程序员有责任去确认是否需要使用互斥量。如,若四个线程会更新同样的数据,但仅有一个线程用了互斥量,则数据可能会损坏。
为了方便演示,我们暂时补充下另外一个函数pthread_join
上面我们提到了一个问题,那就是线程创建后,如何避免主线程先于子线程结束,并且附了一篇参考文章。
线程创建后,避免主线程先于子线程结束的四种方式_怎么让新创建的线程不随主线程结束运行-CSDN博客
在前一个代码例程中,我们是直接使用pthread_exit(NULL)来退出了主线程。
这里,为了演示线程安全问题,我们需要等待其他线程结束,最后才退出主线程。
使用的函数就是pthread_join
linux中pthread_join()与pthread_detach()详解_pthread detach join-CSDN博客
pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。
#include <pthread.h> int pthread_join(pthread_t thread, void **value_ptr);
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值,如果不关心的话,设置为NULL即可。
查看如下程序示例:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #define D_EXE_COUNT 100000 static int count = 0; void *thread_fun1(void *param) { for(int i = 0; i < D_EXE_COUNT; i++) { count++; } return NULL; } void *thread_fun2(void *param) { for(int i = 0; i < D_EXE_COUNT; i++) { count++; } return NULL; } int main(int argc, char *argv[]) { pthread_t tid1, tid2; int ret; ret = pthread_create(&tid1, NULL, thread_fun1, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } ret = pthread_create(&tid2, NULL, thread_fun2, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } //waiting for thread end ret = pthread_join(tid1, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } ret = pthread_join(tid2, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } //print result printf("count = %d\n", count); return 0; }
结果如下
理论上的结果应该是200000,结果明显不对。
这里面其实就存在线程安全的问题。
怎么解决呢?
使用互斥锁即可。
具体内容直接参考《【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.4》互斥锁部分。
本文不再赘述。
也可以直接参考这篇文章:
Posix多线程编程学习笔记 - 凌峰布衣 - 博客园 (cnblogs.com)
总的来说,使用互斥锁只需要以下几个步骤:
1、定义互斥锁对象pthread_mutex_t mutex;;
2、初始化互斥锁对象pthread_mutex_init(&mutex, NULL); ;
3、对共享资源部分进行上锁pthread_mutex_lock(&mutex);和解锁pthread_mutex_unlock(&mutex);
4、可选,最后不用时可以手动销毁pthread_mutex_destroy(&mutex);
示例代码如下:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #define D_EXE_COUNT 100000 static pthread_mutex_t mutex;//in fact,mutex is a global var,but handle for thread safe static int count = 0; void *thread_fun1(void *param) { for(int i = 0; i < D_EXE_COUNT; i++) { pthread_mutex_lock(&mutex); count++; pthread_mutex_unlock(&mutex); } return NULL; } void *thread_fun2(void *param) { for(int i = 0; i < D_EXE_COUNT; i++) { pthread_mutex_lock(&mutex); count++; pthread_mutex_unlock(&mutex); } return NULL; } int main(int argc, char *argv[]) { pthread_t tid1, tid2; int ret; //init mutex pthread_mutex_init(&mutex, NULL); ret = pthread_create(&tid1, NULL, thread_fun1, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } ret = pthread_create(&tid2, NULL, thread_fun2, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } //waiting for thread end ret = pthread_join(tid1, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } ret = pthread_join(tid2, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } //print result printf("count = %d\n", count); return 0; }
结果如下:
可以看到,结果是对的。
注意,同一个共享资源,只要线程操作了,就得上锁,并且,上的必须是同一把锁。
当保护共享数据时,程序员有责任去确认是否需要使用互斥量。如,若四个线程会更新同样的数据,但仅有一个线程用了互斥量,则数据可能会损坏。
经验证,去掉一个线程中的锁,结果如下:
思考下:
线程访问共享资源的两个方式,一个是直接在各自线程里访问,另一种是各线程都调用同一个函数,这个函数里操作了共享资源,上述的例程属于第二种情况。
二者有区别吗?
这两种场景很类似,也很容易搞混,捋一捋。
多个线程都调用同一个函数,该函数是不可重入的,也就是说,里面有共享资源,此时,可以给共享资源部分上锁,因此,每次就只会有一个线程访问该共享资源。相当于每个人都需要进同一个门里去拿东西,这时候这个门就上把锁。这种情况比较好处理。
但是,另外一种情况下,各个线程直接操作的共享资源,首先,线程A操作共享资源时,是有可能被线程B抢占执行的,这时候就会出现线程安全的问题。那么,这种情况怎么解决呢?是给每个线程里的操作部分都给上个锁?换另一种思路就是,如果没法在被调用函数里面上锁,那么如何在调用处解决这个线程同步的问题?这里我有个想法,是不是,只要是同一把锁,同一时间就只能有一把钥匙?如果多个线程里直接操作了同一个全局变量,那么在各自线程里都给操作部分上锁,因为是同一把锁,所以,同时只有一个线程能够处于执行中,只有当线程释放锁之后,其他线程才能进入,就算是在各自线程里操作。如果是这样的话,那就可以解决这个问题了。
这个问题的关键是:使用的是同一把锁。
自旋锁
内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:
一个是原地等待
一个是挂起当前进程,调度其他进程执行(睡眠)
Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源)。
在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。
这里为什么把中断上下文标红加粗呢?因为在中断上下文,是不允许睡眠的(原因详见文章《Linux 中断之中断处理浅析》中的第四章),所以,这里需要的是一个不会导致睡眠的锁——spinlock。
换言之,中断上下文要用锁,首选 spinlock。
更多参考:
条件变量
条件变量是线程可用的另一种同步机制。条件变量用于自动阻塞线程,直到某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的。
注意,为什么要配合互斥锁一起使用呢?
因为条件变量只是一种机制,啥意思呢?就是说,我们自己还得定义一个“条件变量”,当满足条件时,然后通知其他线程,其他等待的线程就会被唤醒。
所以说,这个机制本身只是提供通知和阻塞等待的功能,而并不提供“条件”。
感觉这个机制设计得比较奇怪。
这种情况下,如果我们自己就使用一个标志位,行不行呢?也可以,就是使用标志位的地方会一直轮询标志位,故而造成 CPU 资源的浪费,而使用条件变量机制,就可以在等到时阻塞,条件满足时就会被唤醒。类似于“信号”机制。
使用条件变量主要包括两个动作:
⚫ 一个线程等待某个条件满足而被阻塞;
⚫ 另一个线程中,条件满足时发出“信号”。
条件变量允许一个线程休眠(阻塞等待)直至获取到另一个线程的通知(收到信号)再去执行自己的操作。
前面说到,条件变量通常搭配互斥锁来使用,是因为条件的检测是在互斥锁的保护下进行的,这里说的条件其实就是我们自己定义的一个标志性变量。
说实话,条件变量这个名字真的很容易让人产生误解。不知道是不是翻译的有问题。
参考这两篇文章加深理解:
深入解析条件变量(condition variables) - HarlanC - 博客园 (cnblogs.com)
具体内容直接参考《【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.4》条件变量部分。
本文不再赘述。
这里实际做个测试。
现在想要用两个线程打印一句话
static void *thread_fun1(void *param) { while(1) { printf("this is a "); usleep(100000); } return NULL; } static void *thread_fun2(void *param) { while(1) { printf("test for condition var\n"); printf("---------------------------------\n"); usleep(100000); } return NULL; }
我希望总是先执行线程1,再执行线程2;
如果不干预,显然是会混乱的。
怎么办呢?
可以先参照裸机开发中用个标志位来搞定。
static void *thread_fun1(void *param) { while(1) { if(flag == 0) { printf("this is a "); flag = 1; } } return NULL; } static void *thread_fun2(void *param) { while(1) { if(flag == 1) { printf("test for condition var\n"); printf("---------------------------------\n"); flag = 0; } } return NULL; }
这样其实也可以解决。
但是,上面也说了,这样其实每个线程都在不断地执行两个循环判断,永无休止地在执行,浪费cpu资源。
因此,我们用条件变量来优化一下看看。
static void *thread_fun1(void *param) { while(1) { pthread_mutex_lock(&mutex); if(flag == 0) { printf("this is a "); flag = 1; pthread_cond_signal(&cond);//send signal to conditon var } pthread_mutex_unlock(&mutex); } return NULL; } static void *thread_fun2(void *param) { while(1) { pthread_mutex_lock(&mutex); while(flag == 0) { pthread_cond_wait(&cond, &mutex);//waiting for condition arrive } if(flag == 1) { printf("test for condition var-%d\n", count++); printf("---------------------------------\n"); flag = 0; } pthread_mutex_unlock(&mutex); } return NULL; }
感觉哪里怪怪的,貌似和直接使用标志位区别不大,最大的不同就是,线程2当条件不满足时是阻塞的,而且,这跟我自己在这死等有啥区别?其他没啥变化,反而相比标志位更麻烦。
不知道是不是用法不对。。。。。。。
暂且不管了吧。
在这份示例代码中,我们使用 了 while 循环、而不是 if 语句,来控制对 pthread_cond_wait()的调用,这是为何呢?
必须使用 while 循环,而不是 if 语句,这是一种通用的设计原则:当线程从 pthread_cond_wait()返回时,并不能确定判断条件的状态,应该立即重新检查判断条件,如果条件不满足,那就继续休眠等待。
更多待补充吧。。。
实际中,当需要满足的条件比较复杂时,可以采用这种条件变量。
信号量
Linux中,信号量是实际开发中,实现线程同步的常用方式。
注:互斥锁处理互斥访问或者原子操作的问题;条件变量和信号量解决的是线程同步的问题。宏观上,应该都可以称为线程同步的问题,因为本质都是让线程有先后顺序。不必过于纠结。
在linux中,线程就相当于一个轻量级的进程,它常常被用来完成某种特定功能的事情。假如一个进程创建了多个线程,这些线程要一起配合完成一件更大的事情,这个时候就需要用到线程同步机制了。在Linux中通常用信号量实现线程间的同步。
信号量分为有名信号量和无名信号量,实际上线程间同步用的是无名信号量,进程间同步才用有名信号量(也可以用无名信号量进行进程间同步)。
使用信号量需要包含头文件semaphore.h,且编译时需要链接库-lrt或者-lpthread。
主要参考:
linux线程同步方式3——信号量(semaphore)_linux c++ 跨进程的信号量semaphore.h-CSDN博客
补充参考:
无名信号量——线程间同步 - Suzkfly - 博客园 (cnblogs.com)
信号量基本操作如下:
- 信号量初始化
- 发送信号量
- 将信号量值加 1
- 等待信号量
- 当信号量值为 0 时,程序等待;当信号量值大于 0 时,信号量减 1,程序继续运行。
- 销毁信号量。
对应的posix的API如下:
信号量初始化sem_init
对由sem指定的信号量进行初始化,设置好它的共享选项,并指定一个整数类型的初始值
int sem_init (sem_t *sem , int pshared, unsigned int value);
调用成功时返回0,失败返回-1.
sem:初始化的信号量
pshared:信号量共享的范围。0:线程间使用。
非0:进程间使用。
value:信号量的初始值;
信号量等待sem_wait
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。
如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。直到该信号量值为非0值,相当于P操作。
int sem_wait(sem_t *sem);
调用成功时返回0,失败返回-1.
sem:指向的对象是由sem_init调用初始化的信号量
发送(释放)信号量sem_post
以原子操作的方式将信号量的值加1。当信号量的值大于0时,其它正在调用sem_wait等待信号量的线程将被唤醒,选择机制同样是由线程的调度策略决定的。
int sem_post(sem_t *sem);
调用成功时返回0,失败返回-1.
sem:指向的对象是由sem_init调用初始化的信号量
销毁信号量 sem_destroy
用于对用完的信号量的清理
int sem_destroy(sem_t *sem);
sem:是要销毁的信号量
只有用sem_init初始化的信号量才能用sem_destroy销毁。
使用演示
我们还是以上述打印一句完整的话为例来讲解信号量。
需要包含头文件:#include <semaphore.h>
一开始的程序是这样的:
static void *thread_fun1(void *param) { while(1) { printf("this is a "); sem_post(&sem); } return NULL; } static void *thread_fun2(void *param) { while(1) { sem_wait(&sem); printf("test for condition var\n"); printf("---------------------------------\n"); } return NULL; }
发现有问题
问题在哪呢?那就是执行线程1的时候,可能程序会执行好多次,然后一直在释放信号量,信号量的值就会很大,当线程2获取到信号量的时候,又因为信号量可能很大,所以会连续打印很多次。因此没办法连成一句完整的话。
我的本意是,当线程1执行一次后,就线程2执行,然后反复循环,每次都是打印一句完整的话。
需要改进,让线程1执行一次后,阻塞一段时间,让线程2有机会被执行。
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> static sem_t sem; static void *thread_fun1(void *param) { int sval = 0; while(1) { printf("this is a "); sem_post(&sem); sem_getvalue(&sem, &sval); if(sval == 1) { sleep(1); } } return NULL; } static void *thread_fun2(void *param) { while(1) { sem_wait(&sem); printf("test for condition var\n"); printf("---------------------------------\n"); } return NULL; } int main(int argc, char *argv[]) { pthread_t tid1, tid2; int ret; //init semaphore ret = sem_init(&sem, 0, 0); if (ret < 0) { printf("sem_init failed\n"); exit(-1); } ret = pthread_create(&tid1, NULL, thread_fun1, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } ret = pthread_create(&tid2, NULL, thread_fun2, NULL); if (ret < 0) { printf("ERROR; return code from pthread_create() is %d\n", ret); exit(-1); } pthread_exit(NULL); }
相对条件变量来说,好用很多。
另外,补充下上述涉及到的一个测试信号量函数sem_getvalue
int sem_getvalue(sem_t *sem, int *sval);
获取信号量 sem 的当前值,把该值保存在 sval,若有 1 个或者多个线程正在调用 sem_wait 阻塞在该信号量上,该函数返回阻塞在该信号量上进程或线程个数。
补充
编译时报错:
undefined reference to 'pthread_create'
参考:
Linux下undefined reference to ‘pthread_create’问题解决-CSDN博客
问题原因:
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
问题解决:
在编译中要加 -lpthread参数
gcc thread.c -o thread -lpthread
thread.c为你些的源文件,不要忘了加上头文件#include<pthread.h>