线程互斥锁
一、关于线程互斥锁
线程的主要优势在于能够通过全局变量来共享信息,不过这种便捷的共享是有代价的
:
必须确保多个线程不会同时修改同一变量
某一线程不会读取正由其他线程修改的变量,实际就是
不能让两个线程同时对临界区进行访问
线程互斥锁则可以用于解决多线程资源竞争问题
示例代码:
创建两个子线程,定义一个全局变量
global = 0,
子线程对此全局变量进行加
1
操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int global = 0;
//线程执行函数
void* do_thread(void* argv)
{
//循环对global进行加1操作
int loops = *(int*)argv;
for(int i=0;i<loops;i++)
{
int temp = global;
temp++;
global=temp;
}
pthread_exit(NULL);
}
int main(int argc,char* argv[])
{
if(argc !=2)
{
fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
exit(EXIT_FAILURE);
}
// 获取循环次数
int loopCount = atoi(argv[1]);
// 循环的方式创建两个线程
pthread_t tids[2]={0};
int err;
for(int i=0;i<2;i++)
{
err = pthread_create(tids+i,NULL,do_thread,&loopCount);
if(err!=0)
{
fprintf(stderr,"pthread_create
failed:%s\n",strerror(err));
exit(EXIT_FAILURE);
}
}
pthread_join(tids[0],NULL);
pthread_join(tids[1],NULL);
// 打印全局变量
printf("global=%d\n",global);
return 0;
}
将数字字符串转换为整数
int atoi(const char *nptr);
二、线程互斥锁
1.
线程互斥锁工作机制
当线程
A
获得锁,另外一个线程
B
在获得锁时则会阻塞,直到线程
A
释放锁,线程
B
才会获得锁。
2.
线程互斥锁工作原理
本质上是一个
pthread_mutex_t
类型的变量,假设名为
v
当
v = 1,
则表示当前临界资源可以竞争访问 ,得到互斥锁的线程则可以访问,此时
v = 0
当
v = 0,
则表示临界资源正在被某个线程访问,其他线程则需要等待
3.
线程互斥锁的特点
互斥锁是一个
pthread_mutex_t
类型的变量,就代表一个
互斥锁
如果两个线程访问的是同一个
pthread_mutex_t
变量,那么它们访问了同一个互斥锁
对应的变量定义在
pthreadtypes.h
头文件中,是一个共用体中包含一个结构体
三、线程互斥锁的初始化
线程互斥锁的初始化方式主要分为两种
1.
静态初始化
定义
pthread_mutex_t
类型的变量,然后对其初始化为
PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2.
动态初始化
动态初始化主要涉及两个函数
pthread_mutex_init
函数 与
pthread_mutex_destroy
函数
pthread_mutex_init
函数
获取锁的函数:
pthread_mutex_lock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const
pthread_mutexattr_t *restrict attr);
函数功能
初始化线程互斥锁
函数参数
mutex:
线程互斥锁对象指针
attr:
线程互斥锁属性
函数返回值
成功
:
返回
0
失败
:
返回错误编码
pthread_mutex_destroy
函数
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能
销毁线程互斥锁
函数参数
mutex:
线程互斥锁指针
函数返回值
成功
:
返回
0
失败
:
返回错误编码
四、线程互斥锁的操作
线程互斥锁的操作主要分为
获取锁
(lock)
与
释放锁
(unlock)
获取锁的函数:
pthread_mutex_lock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能
将互斥锁进行锁定,如果已经锁定,则阻塞线程
函数参数
mutex:
线程互斥锁指针
函数返回值
成功
:
返回
0
失败
:
返回错误码
释放锁的函数:
pthread_mutex_unlock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数功能
解除互斥锁锁定状态,解除后,所有线程可以重新竞争锁
函数参数
mutex:
线程互斥锁对象的指针
函数返回值
成功
:
返回
0
失败
:
返回错误码
五、示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int
global
=
0
;
//
互斥锁静态初始化
pthread_mutex_t mtx
=
PTHREAD_MUTEX_INITIALIZER
;
//
线程执行函数
void*
do_thread
(
void*
argv
)
{
//
循环对
global
进行加
1
操作
int
loops
= *
(
int*
)
argv
;
for
(
int
i
=
0
;
i
<
loops
;
i
++
)
{
//
获取锁,一旦获取成功,则获取临界资源的访问资格,否则会阻塞当前线程
pthread_mutex_lock
(
&
mtx
);
int
temp
=
global
;
temp
++
;
global
=
temp
;
//
释放锁
pthread_mutex_unlock
(
&
mtx
);
}
pthread_exit
(
NULL
);
}
int
main
(
int
argc
,
char*
argv
[])
{
if
(
argc
!=
2
)
{
fprintf
(
stderr
,
"arguments must be 2:< cmd > <count>\n"
);
exit
(
EXIT_FAILURE
);
}
//
获取循环次数
int
loopCount
=
atoi
(
argv
[
1
]);
//
循环的方式创建两个线程
pthread_t tids
[
2
]
=
{
0
};
int
err
;
for
(
int
i
=
0
;
i
<
2
;
i
++
)
{
err
=
pthread_create
(
tids
+
i
,
NULL
,
do_thread
,
&
loopCount
);
if
(
err
!=
0
)
{
fprintf
(
stderr
,
"pthread_create
failed:%s\n"
,
strerror
(
err
));
exit
(
EXIT_FAILURE
);
}
}
pthread_join
(
tids
[
0
],
NULL
);
pthread_join
(
tids
[
1
],
NULL
);
//
打印全局变量
printf
(
"global=%d\n"
,
global
);
return
0
;
}
<1>
动态初始化线程互斥锁的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int
global
=
0
;
//
互斥锁变量
pthread_mutex_t mtx
;
//
线程执行函数
void*
do_thread
(
void*
argv
)
{
//
循环对
global
进行加
1
操作
int
loops
= *
(
int*
)
argv
;
for
(
int
i
=
0
;
i
<
loops
;
i
++
)
{
//
获取锁
pthread_mutex_lock
(
&
mtx
);
int
temp
=
global
;
temp
++
;
global
=
temp
;
//
释放锁
pthread_mutex_unlock
(
&
mtx
);
}
pthread_exit
(
NULL
);
}
int
main
(
int
argc
,
char*
argv
[])
{
if
(
argc
!=
2
)
{
fprintf
(
stderr
,
"arguments must be 2:< cmd > <count>\n"
);
exit
(
EXIT_FAILURE
);
}
pthread_mutex_init
(
&
mtx
,
NULL
);
//
获取循环次数
int
loopCount
=
atoi
(
argv
[
1
]);
//
循环的方式创建两个线程
pthread_t tids
[
2
]
=
{
0
};
int
err
;
for
(
int
i
=
0
;
i
<
2
;
i
++
)
{
err
=
pthread_create
(
tids
+
i
,
NULL
,
do_thread
,
&
loopCount
);
if
(
err
!=
0
)
{
fprintf
(
stderr
,
"pthread_create
failed:%s\n"
,
strerror
(
err
));
exit
(
EXIT_FAILURE
);
}
}
pthread_join
(
tids
[
0
],
NULL
);
pthread_join
(
tids
[
1
],
NULL
);
pthread_mutex_destroy
(
&
mtx
);
//
打印全局变量
printf
(
"global=%d\n"
,
global
);
return
0
;
}
线程同步
一、线程同步的概念
线程同步
:是指在互斥的基础上,通过其它机制实现访问者对
资源的有序访问
。
条件变量
:线程库提供的专门针对线程同步的机制
线程同步比较典型的应用场合就是
生产者与消费者
二、生产者与消费者模型原理
1.
在这个模型中,包括
生产者线程
与
消费者线程
。通过线程来模拟多个线程同步的过程
2.
在这个模型中
,
需要以下组件
仓库
:
用于存储产品,一般作为共享资源
生产者线程
:
用于生产产品
消费者线程
:
用于消费产品
3.
原理
当仓库没有产品时,则消费者线程需要等待,直到有产品时才能消费
当仓库已经装满产品时,则生产者线程需要等待,直到消费者线程消费产品之后
三、生产者与消费者模型同步
示例:基于互斥锁实现生产者与消费者模型
要求:
1
、主线程作为消费者线程
2
、
n
个子线程作为生产者线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
// 生产产品的数量
int cnt = atoi((char*)arg);
for(int i=1;i<=cnt;i++)
{
// 获取锁
pthread_mutex_lock(&mutex);
printf("thread[%ld] produces a product, the number of
products is %d.\n",pthread_self(),++product_number);
// 释放锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
int total_of_production= 0;// 生产产品的数量
int total_of_consumption=0;// 消费产品的数量
if(argc < 2)
{
fprintf(stderr,"command argument: ./a.out <...product
quantity>\n");
exit(EXIT_FAILURE);
}
pthread_t tid;
for(int i=1;i<argc;i++)
{
total_of_production += atoi(argv[i]);
int err = pthread_create(&tid,NULL,thread_handle,
(void*)argv[i]);
if(err > 0)
{
fprintf(stderr,"pthread_create
error:%s\b",strerror(err));
exit(EXIT_FAILURE);
}
}
// 消费产品
while(1)
{
pthread_mutex_lock(&mutex);
while(product_number > 0)
{
printf("consumption a product ,the number of products is %d.\n",--product_number);
total_of_consumption++;
sleep(1);
}
pthread_mutex_unlock(&mutex);
if(total_of_production == total_of_consumption)
{
break;
}
}
return 0;
}
条件变量
一、条件变量简介
上面示例代码的不足:
主线程
(
消费者线程
)
需要不断查询是否有产品可以消费,如果没有产品可以消费,也在运行程序,包括获
得互斥锁、判断条件、释放互斥锁,非常消耗
cpu
资源。
使用条件变量进行修改
条件变量
允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知
二、条件变量初始化
条件变量的本质为
pthread_cond_t
类型的变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞
在这个条件变量上的线程。
typedef union
{
struct __pthread_cond_s __data;
char __size[__SIZEOF_PTHREAD_COND_T];
__extension__ long long int __align;
} pthread_cond_t;
条件变量的初始化分为静态初始化与动态初始化
1.
静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.
动态初始化
pthread_cond_init
函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_init(pthread_cond_t *restrict cond,const
pthread_condattr_t *restrict attr);
函数功能
初始化条件变量
函数参数
cond:条件变量指针
attr:条件变量属性
函数返回值
成功:返回0
失败:返回错误码
pthread_cond_destroy函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_destroy(pthread_cond_t *cond);
函数功能
销毁条件变量
函数参数
cond:条件变量指针
函数返回值
成功:返回0
失败:返回错误码
三、条件变量原理
3.1
原理分析
基于条件变量的阻塞与唤醒,具体的原理如下图
step 1
:消费者线程判断消费条件是否满足
(
仓库是否有产品
)
,如果有产品则可以消费,然后解锁
step 2
:当条件满足时
(
仓库产品数量为
0)
,则调用
pthread_cond_wait
函数
,
这个函数具体做的事
情如下
:
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时
,该函数重新竞争锁,并获取锁
step 3
:重新判断条件是否满足,如果满足,则继续调用
pthread_cond_wait
函数
step 4
:唤醒后,从
pthread_cond_wait
返回,条件不满足,则正常消费产品
step 5
:释放锁,整个过程结束
示例代码:基于条件变量实现生产者与消费者模型
(
多个生产者对应一个消费者
)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//条件变量的静态初始化
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
// 生产产品的数量
int cnt = atoi((char*)arg);
for(int i=1;i<=cnt;i++)
{
// 获取锁
pthread_mutex_lock(&mutex);
printf("thread[%ld] produces a product, the number of products is %d.\n",pthread_self(),++product_number);
// 唤醒消费者线程
pthread_cond_signal(&cond);
// 释放锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
int total_of_production= 0;// 生产产品的数量
int total_of_consumption=0;// 消费产品的数量
if(argc < 2)
{
fprintf(stderr,"command argument: ./a.out <...product quantity>\n");
exit(EXIT_FAILURE);
}
pthread_t tid;
for(int i=1;i<argc;i++)
{
total_of_production += atoi(argv[i]);
int err = pthread_create(&tid,NULL,thread_handle,(void*)argv[i]);
if(err > 0)
{
fprintf(stderr,"pthread_create error:%s\b",strerror(err));
exit(EXIT_FAILURE);
}
}
// 消费产品
while(1)
{
pthread_mutex_lock(&mutex);
// 使用条件变量进行阻塞
while(product_number==0)
{
pthread_cond_wait(&cond,&mutex);
}
while(product_number > 0)
{
printf("consumption a product ,the number of products is %d.\n",--product_number);
total_of_consumption++; sleep(1);
}
pthread_mutex_unlock(&mutex);
if(total_of_production == total_of_consumption)
{
break;
}
}
return 0;
}
3.2 pthread_cond_wait
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict
mutex);
函数功能
阻塞线程,等待唤醒
函数参数
cond:条件变量指针
mutex:相关联的互斥锁指针
函数返回值
成功:返回 0
失败:返回错误码
条件变量需要与互斥锁结合使用,先获得锁才能进行条件变量的操作
调用函数后会释放锁,并阻塞线程
一旦线程唤醒,需要重新竞争锁,重新获得锁之后,
pthread_cond_wait
函数返回
3.3 pthread_cond_signal
和
pthread_cond_broadcast
pthread_cond_signal
函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
函数返回值
成功:返回0
失败:返回错误码
pthread_cond_broadcast
函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_broadcast(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
函数参数
cond:
条件变量指针
函数返回值
成功
:
返回
0
失败
:
返回错误码
pthread_cond_signal
函数主要
适用等待线程都在执行完全相同的任务
pthread_cond_broadcast
函数
主要适用等待线程都执行不相同的任务
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制,发送信号时若无任何线
程在等待该条件变量,则会被忽略
条件变量代表是一个通讯机制,用于传递通知与等待通知,用户可以设定条件来发送或者等待通知
四、问题
4.1
为什么条件变量需要与互斥锁结合起来使用
?
答:防止在调用
pthread_cond_wait
函数等待一个条件变量收到唤醒信号,另外一个线程发送信号在第
一个线程实际等待它之前,也就是说线程还没有完全进入到睡眠状态,其他线程发送唤醒信号。
4.2
在判断条件时
,
为什么需要使用
while(number == 0),
而不是
if()
答:防止虚假唤醒。
能够唤醒的情况如下
:
被信号唤醒,并非由条件满足而唤醒
条件变量状态改变时,一次唤醒多个线程,但是被其他线程先消费完产品,等到当前线程执行时,
条件已经不满足
补充: