线程概念
什么是线程
LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)
进程:独立地址空间,拥有PCB(进程控制块PCB(Process Control Block)
线程:也有PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间。 独居(进程);合租(线程)。
Linux下: 线程:最小的执行单位,调度的基本单位。
进程:最小分配资源单位,可看成是只有一个线程的进程。
在linux下,线程最是小的执行单位;进程是最小的分配资源单位
线程共享资源
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户ID和组ID
5.内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源
1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级
线程优、缺点
优点:
1、提高程序并发性
2、开销小
3、数据通信、共享数据方便
缺点:
1、库函数,不稳定
2、调试、编写困难、gdb不支持
3、对信号支持不好
优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
线程相关操作函数
数据类型转换
1、void *arg---->int i=0;
i=*( (int *)arg );
int i=1;---->void *arg
&i-->int * (&i)===>i;
pthread_create(&thread, NULL, start_routine, (void *)&i)
2、int *ret[i];--->int
ret[i]-->int *---->int *p中的p(地址)
*ret[i]----》int
3、int --->void *
int var;
pthread_exit(void *arg)
pthread_exit((void *)&var);
4、int *ret[i];----》void **类型
ret[i]--->int *类型
&ret[i]--》int类型
(void **)ret[i]---》void **类型
int pthread_join(pthread_t thread, void **retval);
pthread_join(tid[i], (void **)&ret[i])
//函数指针;void *(*start_routine) (void *)
//指针函数: void *start_routine (void *arg)
创建线程程序例程1:
//错误判断--》等价于if(){perror(“”);return -1}
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
int *p=NULL;
//清理函数:
void routine(void *arg)
{
free(p);
printf("this is routine free\n");
}
//子线程
void *start_routine (void *arg)
{
int i=0;
static int stat=1;
int oldstate=0;
int oldtype=0;
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
/*
PTHREAD_CANCEL_DISABLE //不会被取消
在使用 PTHREAD_CANCEL_ENABLE前提下:pthread_setcanceltype(参数设置为:)
PTHREAD_CANCEL_DEFERRED//延時取消
(该线程开始并不取消;等到下个取消点到来结束线程)
PTHREAD_CANCEL_ASYNCHRONOUS//立即取消
*/
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);//可以取消
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);//立即取消
//入栈 ---》清除资源成对出现
pthread_cleanup_push(routine,NULL);
p=(int *)malloc(sizeof(int));
for(i=0;i<5;i++)
{
printf("this is my first pthread,the id is%lu\n",pthread_self());
sleep(1);
}
free(p);
printf("free success\n");
//出栈
pthread_cleanup_pop(1);
//退出子线程;返回子线程状态:stat
pthread_exit((void *)&stat);
}
int main(int argc, char const* argv[])
{
int i=0;
pthread_attr_t attr;
pthread_t thread=0;
int num=10;
void *stat=(void *)0;
/*
1.设置属性---》初始化 attr:线程属性 pthread_attr_init(&attr);
2.设置线程分离属性 : int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
PTHREAD_CREATE_DETACHED:分离
PTHREAD _CREATE_JOINABLE:非分离
3.设置绑定属性 :int pthread_attr_setscope(pthread_attr_t *attr, int scope);
PTHREAD_SCOPE_SYSTEM:绑定
PTHREAD_SCOPE_PROCESS:非绑定
*/
//分离,非绑定
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_attr_setscope(&attr,PTHREAD_SCOPE_PROCESS);
//创建子线程
int ret=pthread_create(&thread,&attr,start_routine,(void *)&num);
if(ret!=0)
{
handle_error_en(ret,"pthread_create");
}
for(i=0;i<5;i++)
{
printf("this is main thread,son id is %lu\n",thread);
if(i==2)
//终止线程
pthread_cancel(thread);
sleep(1);
}
/*若使用下面语句--》运行报错--》核心已转储--》原因: pthread_exit((void *)&stat)还未将数值传入stsat就被终止----》pthread_join(thread,(void *)&stat);获取不到子线程状态*/
//pthread_detach(thread);
//pthread_join(thread,(void *)&stat);
//printf("%d\n",*(int *)stat);
pthread_join(thread,NULL);
return 0;
}
创建线程程序例程2:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//var:全局变量----》子线程状态
int var = 100;
//全局变量---》在子线程中开辟空间但未 释放
int *p=NULL;
void routine(void *arg)
{
free(p);
printf("this is routine free\n");
}
//创建的5个子线程
void *tfn(void *arg)
{
//入栈
pthread_cleanup_push(routine,NULL);
p=(int *)malloc(sizeof(int));
free(p);
//出栈
pthread_cleanup_pop(1);
printf("free success\n");
int i;
i = (int)arg;
sleep(i);
//线程1: i为函数传参
if (i == 1)
{
var = 1;
printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i, pthread_self(), var);
//返回线程1状态 ---同 pthread_exit((void *)var);
return (void *)&var;
}
//线程3:
else if (i == 3)
{
printf("hello\n");
var = 3;
printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i, pthread_self(), var);
//返回线程3状态
pthread_exit((void *)&var);
}
// 线程2/4/5
else
{
printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i, pthread_self(), var);
pthread_exit((void *)&var);
}
return NULL;
}
//主线程
int main(void)
{
//创建多个线程----》数组保存多个线程ID
pthread_t tid[5];
int i;
//数组保存接受的多个线程状态:
int *ret[5];
//创建5个线程 第一个参数:pthread_t *thread; 第三个参数:子线程函数
for (i = 0; i < 5; i++)
pthread_create(&tid[i], NULL, tfn, &i);
//分别获取子线程(0;1;2;3;4)状态
for (i = 0; i < 5; i++)
{
if (i!=1)
{
//除线程2;其余进程全部解除与主进程关联
pthread_detach(tid[i]);
//取消i==3(第四个进程)
if (i==3)
{
pthread_cancel(tid[i]); //自己添加取消点*/
}
}
//第二个线程(i==1时进入else)
else
{
//阻塞父线程;等待子线程退出,获取线程退出状态
pthread_join(tid[i], (void **)&ret[i]);
//打印出子线程状态
printf("-------%d 's ret = %d\n", i, (int)ret[i]);
}
}
printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);
sleep(i);
return 0;
}
线程私有数据 (不同线程对同一键值取不同的值)
线程私有数据 (Thread-specific Data,或称为 TSD):
线程私有数据采用一键多值的技术,即一个键对应多个数值。
通过键值来访问数据,看似对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。
线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个数值
线程私有数据相关API
相关操作:
下面接口所需头文件:
#include <pthread.h>
1)创建线程私有数据
2)注销线程私有数据
3)设置线程私有数据的关联
4)读取线程私有数据所关联的值
程序例程:
/*
程序运行结果
I'm 1th pthread, id = 139960459020032 var = 1 setspecific_value=111
FUN1
第1个子线程返回给主线程状态为1
线程4读取私有数据为:0
I'm 4th pthread, id = 139960433841920 var = 4 setspecific_value=444
FUN1
第4个子线程返回给主线程状态为4
*/
#include "stdio.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//var:全局变量----》子线程状态
int var = 100;
//创建私有数据 ----》线程创建:
pthread_key_t key;
//线程1私有数据清理函数1:
void fun1 (void *arg)
{
printf("FUN1\n");
}
//线程2私有数据清理函数2:
void fun2 (void *arg)
{
printf("FUN2\n");
}
//创建的5个子线程
void *tfn(void *arg)
{
int i;
i = (int)arg;
//不同的睡眠时间--》运行结果不同??????
sleep(i);
//线程1: i为函数传参
if (i == 1)
{
int value=111;
var = 1;
//2----设置线程私有数据的关联
pthread_setspecific(key, &value);
printf("I'm %dth pthread, id = %lu\t var = %d\t setspecific_value=%d\n", i, pthread_self(), var,*((int *)pthread_getspecific(key)));
//返回线程1状态 ---同 pthread_exit((void *)&var);
pthread_exit((void *)&var);
}
//线程3:
else if (i == 3)
{
var = 3;
printf("I'm %dth pthread, pthread_id = %lu\t var = %d\n", i, pthread_self(), var);
//返回线程3状态
pthread_exit((void *)var);
}
//线程4:读取线程私有数据所关联的值
else if (i== 4)
{
int value=444;
var = 4;
pthread_setspecific(key, &value);
printf("I'm %dth pthread, id = %lu\t var = %d\t setspecific_value=%d\n", i, pthread_self(), var,*((int *)pthread_getspecific(key)));
//返回线程3状态
pthread_exit((void *)var);
}
// 线程2/4/5
else
{
sleep(1);
printf("I'm %dth pthread, pthread_id = %lu\t var = %d\n", i, pthread_self(), var);
pthread_exit((void *)var);
}
return NULL;
}
**//主线程**
int main(void)
{
//创建多个线程----》数组保存多个线程ID
pthread_t tid[5];
int i;
//数组保存接受的多个线程状态:
int *ret[5];
//主函数------------------------------------------创建私有数据:
pthread_key_create(&key, fun1);
//创建5个线程 第一个参数:pthread_t *thread; 第三个参数:子线程函数
for (i = 0; i < 5; i++)
pthread_create(&tid[i], NULL, tfn, (void *)i);
//分别获取子线程(0;1;2;3;4)状态
for (i = 0; i < 5; i++)
{
//取消i==3(第四个进程)
if ( (i==3)||(i==0) )
{
pthread_cancel(tid[i]); //自己添加取消点*/
sleep(1);
}
if (i==2)
{
pthread_cancel(tid[i]); //自己添加取消点*/
sleep(1);
}
//第二个线程(i==1时进入else)
else if ( (i==1)||(i==4) )
{
//阻塞父线程;等待子线程退出,获取线程退出状态
pthread_join(tid[i], (void **)&ret[i]);
//打印出子线程状态
printf(" 第%d个子线程返回给主线程状态为%d\n", i, (int)ret[i]);
}
}
sleep(i);
return 0;
}
线程间的同步和互斥
互斥信号量(互斥锁)与信号量
1、为什么进行同步以及互斥:实现对资源的保护、独占
互斥锁的作用
保护共享数据: 在并发机制的情况下,有时候会有多个线程同时访问同一片数据,为了保护数据操作的准确性就需要通过加锁来进行保护。
保持操作互斥: 可能一个程序会有多个操作,但是同一个时间只能有一个操作被执行,例如a/b两个操作,如果a被执行,b就不能被执行,同理b被执行,a就不能执行操作函数
互斥锁分类
1、快速互斥锁
最常用的锁,符合以上的含义
2、检测锁
快速互斥锁的非阻塞版本
3、递归锁
多次加锁
**
互斥锁相关函数:
**
互斥锁相关操作函数:
1、pthread_mutex_t lock; /* 互斥锁定义 */
2、pthread_mutex_init(&lock, NULL); /* 动态初始化,成功返回0,失败返回非0 */
3、pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化 */
4、pthread_mutex_lock(&lock); /* 互斥锁上锁;阻塞的锁定互斥锁
5、pthread_mutex_trylock(&thread_mutex);/* 互斥锁判断上锁;非阻塞的锁定互斥锁,成功获得互斥锁返回0,如果未能获得互斥锁,立即返回一个错误码
6、pthread_mutex_unlock(&lock); /* 解锁互斥锁/
7、pthread_mutex_destroy(&lock) /* 销毁互斥锁 */
例程1: 互斥锁保护共享数据
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
//全局变量:
static pthread_mutex_t g_mutex_lock;
static int g_count = 0;
static void *thread_fun_1(void *data)
{
//加锁
pthread_mutex_lock(&g_mutex_lock);
g_count++;
printf("%s g_count: %d\n", __func__, g_count);
//解锁
pthread_mutex_unlock(&g_mutex_lock);
}
static void *thread_fun_2(void *data)
{
pthread_mutex_lock(&g_mutex_lock);
g_count++;
printf("%s g_count: %d\n", __func__, g_count);
pthread_mutex_unlock(&g_mutex_lock);
}
static void *thread_fun_3(void *data)
{
pthread_mutex_lock(&g_mutex_lock);
g_count++;
printf("%s g_count: %d\n", __func__, g_count);
pthread_mutex_unlock(&g_mutex_lock);
}
int main(int argc, char const *argv[])
{
int ret;
pthread_t pid[3];
//主线程初始化互斥锁
ret = pthread_mutex_init(&g_mutex_lock, NULL);
if (ret != 0) {
printf("mutex init failed\n");
return -1;
}
//创建3个子线程:
pthread_create(&pid[0], NULL, thread_fun_1, NULL);
pthread_create(&pid[1], NULL, thread_fun_2, NULL);
pthread_create(&pid[2], NULL, thread_fun_3, NULL);
//等待线程结束---阻塞主线程
pthread_join(pid[0], NULL);
pthread_join(pid[1], NULL);
pthread_join(pid[2], NULL);
//删除互斥锁:
pthread_mutex_destroy(&g_mutex_lock);
return 0;
}
/*
加互斥锁:
对数据g_count的操作进行加锁之后,同一个时间只有一个线程能获取到锁,也就是只有一个线程能对g_count进行操作,保证了g_count的数据的准确性打印结果:各进程依次运行
thread_fun_1 g_count: 1
thread_fun_2 g_count: 2
thread_fun_3 g_count: 3
若不加互斥锁:
3个线程都会对g_count进行操作,没有任何保护的情况下,3个线程是存在竞争关系的,所以g_count最终可能会是1、2、3三种值中的一种,
*/
实例2:保持操作的互斥性
有些情况下,2个不同的操作是不能同时进行的,例如fingerprint中的enroll和verify同一时间只能有一个操作进行。保持操作的互斥性本质上其实还是在保护共有的数据。下面的例子,打印hello的时候,world是无法打印的,如果希望打印world只能等待打印hello的线程退出之后再打印
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
static pthread_mutex_t g_mutex_lock;
static void *thread_fun_1(void *data)
{
//阻塞锁:直到获取到锁才结束运行:阻塞其他线程
pthread_mutex_lock(&g_mutex_lock);
int i = 0;
while (i < 5) {
printf("hello\n");
i++;
sleep(1);
}
pthread_mutex_unlock(&g_mutex_lock);
}
static void *thread_fun_2(void *data)
{
pthread_mutex_lock(&g_mutex_lock);
int i = 0;
while (i < 5) {
printf("world\n");
i++;
sleep(1);
}
pthread_mutex_unlock(&g_mutex_lock);
}
static void do_print_hello()
{
pthread_t pth_id;
int result = pthread_create(&pth_id, NULL, thread_fun_1, NULL);
}
static void do_print_world()
{
pthread_t pth_id;
int result = pthread_create(&pth_id, NULL, thread_fun_2, NULL);
}
int main(int argc, char const *argv[])
{
int ret;
int cid;
ret = pthread_mutex_init(&g_mutex_lock, NULL);
if (ret != 0) {
printf("mutex init failed\n");
return -1;
}
printf("0---------------hello\n");
printf("1---------------world\n");
printf("2---------------EXIT\n");
while (1) {
printf("input a num: \n");
scanf("%d", &cid);
getchar();
switch (cid)
{
case 0:
do_print_hello();
break;
case 1:
do_print_world();
break;
case 2:
pthread_mutex_destroy(&g_mutex_lock);
return 0;
default: break;
}
}
}
非阻塞式的锁
上面的互斥锁是阻塞式的锁,也可以通过非阻塞式的锁进行,看下面的例子,pthread_mutex_trylock()函数如果获取到互斥锁了,会返回0,如果没有获取的互斥锁,会立即返回一个非0值,例子中通过g_cancel来通知线程进行退出,如果当前正在打印hello,发出打印world命令之后,通过pthread_mutex_trylock()就能知道当前有没有打印线程正在运行,如果有在运行的线程,通过置位g_cancel来退出正在运行的线程
/*
现象:
当按下打印hello命令时;打印hello;
再次按下打印hello命令会停止打印;按下打印world命令时打印world
world同Hello
原理:
定义全局变量:打印的标志位
针对于打印hello函数:
按下1命令------》开启线程1进入while中先对标志位进行检测;满足条件–》进行打印hello数据
再次按下1/0-----》又打开一个线程2;对标志位进行设置;—》导致线程1检测到标志改变打印停止
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
static pthread_mutex_t g_mutex_lock;
//打印状态标志位:
static int g_cancel = 0;
static void *thread_fun_1(void *data)
{
printf("hello_id :%lu\n",pthread_self());
//非阻塞锁:检测不到锁直接返回0---》打印状态的全局变量值改变
if (pthread_mutex_trylock(&g_mutex_lock) != 0) {
printf("11111\n");
g_cancel = 1;
return 0;
}
int i = 0;
g_cancel = 0;
while (i < 5) {
//其他线程对全局变量打印状态的值改变时;该线程停止打印
if (g_cancel) break;
printf("hello\n");
i++;
sleep(1);
}
pthread_mutex_unlock(&g_mutex_lock);
}
static void *thread_fun_2(void *data)
{
printf("world_id :%lu\n",pthread_self());
if (pthread_mutex_trylock(&g_mutex_lock) != 0) {
printf("2222\n");
g_cancel = 1;
return 0;
}
int i = 0;
g_cancel = 0;
while (i < 5) {
if (g_cancel) break;
printf("world\n");
i++;
sleep(1);
}
pthread_mutex_unlock(&g_mutex_lock);
}
static void do_print_hello()
{
pthread_t pth_id;
int result = pthread_create(&pth_id, NULL, thread_fun_1, NULL);
}
static void do_print_world()
{
pthread_t pth_id;
int result = pthread_create(&pth_id, NULL, thread_fun_2, NULL);
}
int main(int argc, char const *argv[])
{
int ret;
int cid;
ret = pthread_mutex_init(&g_mutex_lock, NULL);
if (ret != 0) {
printf("mutex init failed\n");
return -1;
}
printf("0---------------hello\n");
printf("1---------------world\n");
printf("2---------------EXIT\n");
while (1) {
printf("input a num: \n");
scanf("%d", &cid);
getchar();
switch (cid) {
case 0:
do_print_hello();
break;
case 1:
do_print_world();
break;
case 2:
pthread_mutex_destroy(&g_mutex_lock);
return 0;
default:
break;
}
}
}
Linux线程的信号量同步
信号量和互斥锁(mutex)的区别:
互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。
信号量相关操作函数:
头文件semaphore.h。
主要用到的函数:
·int sem_init(sem_t *sem, int pshared, unsigned int value);
·sem是要初始化的信号量,
·pshared表示此信号量是在进程间共享还是线程间共享
·value是信号量的初始值。
·int sem_destroy(sem_t *sem) ----》销毁的信号量
·sem是要销毁的信号量。只有用sem_init初始化的信号量才能用sem_destroy销毁。
·int sem_wait(sem_t *sem); -----》等待信号量,
·如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
·int sem_post(sem_t *sem); 释放信号量,
·让信号量的值加1。相当于V操作。
下列的代码演示了如何用信号量同步,模拟一个窗口服务系统。
//@purpose: 基于信号量的多线程同步,操作系统原理中的P,V操作
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define CUSTOMER_NUM 10
/* @Scene: 某行业营业厅同时只能服务两个顾客。
* 有多个顾客到来,每个顾客如果发现服务窗口已满,就等待,
* 如果有可用的服务窗口,就接受服务。 */
/* 将信号量定义为全局变量,方便多个线程共享 */
sem_t sem;
/* 每个线程要运行的例程 */
void * get_service(void *thread_id)
{
/* 注意:立即保存thread_id的值,因为thread_id是对主线程中循环变量i的引用,它可能马上被修改 */
int customer_id = *((int *)thread_id);
//信号量大于0--》进行P操作(信号量-1操作)
if(sem_wait(&sem) == 0)
{
//usleep(100); /* service time: 100ms */
sleep(1);
printf("customer %d receive service ...\n", customer_id);
//进行V操作(信号量+1)
sem_post(&sem);
}
}
int main(int argc, char *argv[])
{
/* 初始化信号量,初始值为2,表示有两个顾客可以同时接收服务 */
/* @prototype: int sem_init(sem_t *sem, int pshared, unsigned int value); */
/* pshared: if pshared == 0, the semaphore is shared among threads of a process
* otherwise the semaphore is shared between processes. */
sem_init(&sem, 0, 2);
/* 为每个顾客定义一个线程id, pthread_t 其实是unsigned long int */
pthread_t customers[CUSTOMER_NUM];
int i, ret;
/* 为每个顾客生成一个线程 */
for(i = 0; i < CUSTOMER_NUM; i++)
{
int customer_id = i;
ret = pthread_create(&customers[i], NULL, get_service, &customer_id);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
else
{
printf("Customer %d arrived.\n", i);
}
usleep(10);
}
/* 等待所有顾客的线程结束 */
/* 注意:这地方不能再用i做循环变量,因为可能线程中正在访问i的值 */
int j;
for(j = 0; j < CUSTOMER_NUM; j++)
{
pthread_join(customers[j], NULL);
}
/* Only a semaphore that has been initialized by sem_init(3)
* should be destroyed using sem_destroy().*/
sem_destroy(&sem);
return 0;
}
编译:gcc sem.c -lpthread
运行结果(注意,每次运行都不相同):
/*
实验现象:窗口每次只接受2人
Customer 0 arrived.
Customer 1 arrived.
Customer 2 arrived.
Customer 3 arrived.
Customer 4 arrived.
Customer 5 arrived.
Customer 6 arrived.
Customer 7 arrived.
Customer 8 arrived.
Customer 9 arrived.
customer 0 receive service ...
customer 1 receive service ...
customer 3 receive service ...
customer 2 receive service ...
customer 4 receive service ...
customer 5 receive service ...
customer 6 receive service ...
customer 7 receive service ...
customer 8 receive service ...
customer 9 receive service ...
*/
linux中的条件变量的使用
什么是条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制。
主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使”条件成立”(给出条件成立信号)。
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
条件变量类型为 pthread_cond_t。
条件变量有什么用
使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的。
如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:
程序实例
条件变量的使用可以分为两部分:
等待线程
使用pthread_cond_wait前要先加锁;
pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活;
pthread_cond_wait被激活后会再自动加锁;
激活线程:
加锁(和等待线程用同一个锁);
pthread_cond_signal发送信号;
解锁;
激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部
/
程序执行结果:
decrement lock
incremen lock
进程2释放条件信号
进程1被唤醒
/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//互斥锁变量定义
pthread_mutex_t count_lock;
//条件变量标志符定义
pthread_cond_t count_nonzero;
unsigned count = 0;
//子线程:
void *decrement_count(void *arg)
{
//加锁
pthread_mutex_lock(&count_lock);
printf("decrement lock\n");
while(count == 0)
{
printf("decrement_count count == 0 \n");
// 用于阻塞当前线程,等待别的线程使用 pthread_cond_signal() 或 pthread_cond_broadcast来唤醒它
// pthread_cond_wait()通过(返回)时,该线程又自动获得该 mutex 。
pthread_cond_wait(&count_nonzero, &count_lock);
printf("进程1被唤醒 \n");
}
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
void *increment_count(void *arg)
{
pthread_mutex_lock(&count_lock);
printf("increment_count lock \n");
if(count == 0)
{
//发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.
//如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
pthread_cond_signal(&count_nonzero);
printf("进程2释放条件信号 \n");
}
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
int main(void)
{
pthread_t tid1, tid2;
//互斥锁与条件变量初始化:
pthread_mutex_init(&count_lock, NULL);
pthread_cond_init(&count_nonzero, NULL);
//创建线程1:
pthread_create(&tid1, NULL, decrement_count, NULL);
sleep(2);
//创建线程2:
pthread_create(&tid2, NULL, increment_count, NULL);
sleep(3);
//等待线程结束
pthread_exit(0);
return 0;
}
- TEACHER 遗留问题: 子线程等待父线程发送信号被唤醒—》成功 父线程等待子线程发送信号唤醒----》失败
#if 1
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
//线程私有数据
pthread_key_t key;
//线程互斥锁
pthread_mutex_t mutex;
//线程条件变量
pthread_cond_t cond;
//
void destr_function (void *arg)
{
printf("this is fun2\n");
}
//子线程
void *starts_routine (void *arg)
{
int i=0,num=20,std=0;
static int stat=0,err=1;
//设置私有数据
pthread_setspecific(key,&num);
//子线程上锁
pthread_mutex_lock(&mutex);
#if 1
//条件变量无条件等待
pthread_cond_wait(&cond,&mutex);
printf("进程1被唤醒 \n");
#endif
for(i=0;i<5;i++)
{
//读取私有数据
std=*(int *)pthread_getspecific(key);
printf("this is my second pthread,the std is %d\n",std);
sleep(1);
}
//解锁
pthread_mutex_unlock(&mutex);
//等待子线程退出;返回子线程状态:stat
pthread_exit((void *)&stat);
}
int main(int argc, char const* argv[])
{
int i=0;
int num=10;
void *stat=(void *)0;
pthread_t thread=0,thread1=0;
//互斥锁初始化
pthread_mutex_init(&mutex,0);
//条件变量初始化
pthread_cond_init(&cond, NULL);
//创建线程私有数据;清理函数:destr_function
pthread_key_create(&key, destr_function);
//创建子线程starts_routine;传入参数:num
int ret1=pthread_create(&thread1,NULL,starts_routine,(void *)&num);
if(ret1!=0)
{
handle_error_en(ret1,"pthread_create");
}
sleep(1);
//主线程上锁
pthread_mutex_lock(&mutex);
printf("主进程被唤醒 \n");
for(i=0;i<5;i++)
{
printf("this is main thread,son id is %lu\n",thread);
if(i==2)
{
//唤醒一个等待条件的线程
pthread_cond_signal(&cond);
printf("主線程释放条件信号 \n");
}
}
//主线程开锁
pthread_mutex_unlock(&mutex);
//等待子线程结束
pthread_join(thread,NULL);
pthread_join(thread1,NULL);
//删除私有数据
pthread_key_delete(key);
return 0;
}
#endif