0%

线程以及线程间的竞态问题解决

本文包含线程的介绍使用,以及线程间的通信方式,解决多线程中的竞态问题。阅读建议对进程有所了解。

线程

线程的概念

线程(LWP):线程是轻量级的进程,进程是分配资源的最小单位(0-3G),线程是调度的最小单位。线程本身不占用资源它是共享进程的资源。线程没有进程安全,因为如果一个线程导致进程结束,其他所有的线程都不能执行。多线程的并发性比多进程的高,因为线程间切换比进程间切换时间短。线程间资源共享,所以线程间通信要比进程间通信更为容易。

1
2
3
ps -ajx #看进程附加态(l,代表多线程)
ps -eLf #多线程
htop #多线程

线程的创建及特点

多线程的创建

多线程创建的接口是第三方库提供的 libpthread.so,所以如果要使用线程相关的函数,在编译的时候就必须链接这个库,线程相关的man手册需要使用apt-get来安装。

1
2
sudo apt-get install manpages-*
gcc xxx.c -lpthread

线程创建的接口

1
2
3
4
5
6
7
8
9
10
11
12
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
// 功能:创建线程
// 参数:
// @thread:线程号指针
// @attr:线程属性(一般填写为NULL)
// @start_routine:线程体(指向线程处理函数)
// @arg: 向线程体传递的参数(如果没有填写为NULL)
// 返回值:成功返回0,失败返回错误码
// Compile and link with -pthread.

线程创建的实例1(不传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <head.h>
void* task1(void* arg)
{
while (1) {
printf("我是子线程...\n");
sleep(1);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

while (1) {
printf("我是主线程...\n");
sleep(1);
}
return 0;
}

线程创建的实例2(传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <head.h>
typedef struct {
char name[20];
char sex;
int age;
}stu_t;
void* task1(void* arg)
{
stu_t *stu = (stu_t *)arg;
printf("name=%s,sex=%c,age=%d\n",stu->name,stu->sex,stu->age);
while (1) {
printf("我是子线程...\n");
sleep(1);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;
stu_t stu = {
.name = "zhangsan",
.sex = 'm',
.age = 20,
};

if ((errno = pthread_create(&tid, NULL, task1, (void *)&stu)) != 0)
PRINT_ERR("pthread_create error");

while (1) {
printf("我是主线程...\n");
sleep(1);
}
return 0;
}

线程创建的实例3(全局变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <head.h>
typedef struct {
char name[20];
char sex;
int age;
} stu_t;

stu_t stu = {
.name = "zhangsan",
.sex = 'm',
.age = 20,
};
void* task1(void* arg)
{
printf("child:name=%s,sex=%c,age=%d\n", stu.name, stu.sex, stu.age);
while (1) {
printf("我是子线程...\n");
sleep(1);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;
printf("parent:name=%s,sex=%c,age=%d\n", stu.name, stu.sex, stu.age);
if ((errno = pthread_create(&tid, NULL, task1,NULL))!=0)
PRINT_ERR("pthread_create error");

while (1) {
printf("我是主线程...\n");
sleep(1);
}
return 0;
}

线程创建的实例4(关于资源回收)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <head.h>

void* task1(void* arg)
{
printf("我是子线程...\n");
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

printf("我是主线程...\n");

//如果不写这个sleep(1),有可能主线程执行结束,子线程还没来及执行
//进程就退出了,子线程将不能执行
sleep(1);
return 0;
}

多线程执行顺序

多线程执行没有先后顺序,时间片轮询,上下文切换

多线程内存空间问题

多线程共享进程的内存空间,全局变量多线程都可以访问,也都可以修改。

线程相关的函数

pthread_self 函数

1
2
3
4
5
6
#include <pthread.h>
pthread_t pthread_self(void);
// 功能:获取当前线程的线程号
// 参数:
// @无
// 返回值:总是会成功,返回线程号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <head.h>

void* task1(void* arg)
{
printf("我是子线程...\n");
printf("子:tid = %ld\n",pthread_self());
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

printf("主:tid = %ld,子:tid = %ld\n",pthread_self(),tid);

sleep(1);
return 0;
}

pthread_exit 函数

在线程中一般不调用exit/_exit,因为这些函数会让进程退出,如果进程退出了,所有的线程都将不能执行。所以如果想退出某个线程是用pthread_exit完成。

1
2
3
4
5
6
#include <pthread.h>
void pthread_exit(void *retval);
// 功能:退出线程
// 参数:
// @retval:线程退出的状态
// 返回值:无
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <head.h>

void* task1(void* arg)
{
while(1){
sleep(1);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

printf("主:tid = %ld,子:tid = %ld\n", pthread_self(), tid);

int n = 5;
while (1) {
printf("我是主线程...\n");
sleep(1);
n--;
if (n == 0)
pthread_exit(NULL);
}
return 0;
}

pthread_join 函数

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
// 功能:回收线程的资源(阻塞等子线程结束)
// 参数:
// @thread:线程号
// @retval:接收pthread_exit退出时候的线程状态值
// 返回值:成功返回0,失败返回错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <head.h>

void* task1(void* arg)
{
int n = 5;
static int num=12345;
while (1) {
printf("我是子线程\n");
sleep(1);
n--;
if (n == 0)
pthread_exit((void *)&num);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

int *retval;
pthread_join(tid,(void **)&retval);
printf("retval = %d\n",*retval);
return 0;
}

多线程拷贝文件

在主线程中创建两个子线程,让这两个子线程同时拷贝文件,各拷贝一半。

1
./a.out srcfile destfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <head.h>
typedef struct {
const char* src;
const char* dest;
int start;
int len;
} file_t;
int get_file_len(const char* file)
{
int fd, len;
if ((fd = open(file, O_RDONLY)) == -1)
PRINT_ERR("open error");
len = lseek(fd, 0, SEEK_END);
close(fd);
return len;
}
int init_src_file(const char* file)
{
int fd;
if ((fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1)
PRINT_ERR("open error");

close(fd);
return 0;
}
int copy_file(const char* src, const char* dest, int start, int len)
{
char s[20];
int fd1, fd2;
int ret, count = 0;
// 1.以只读方式打开源文件,以只写方式打开目标文件
if ((fd1 = open(src, O_RDONLY)) == -1)
PRINT_ERR("open src error");
if ((fd2 = open(dest, O_WRONLY)) == -1)
PRINT_ERR("open dest error");
// 2.定位源和目标文件的光标
lseek(fd1, start, SEEK_SET);
lseek(fd2, start, SEEK_SET);
// 3.循环拷贝
while (count < len) {
ret = read(fd1, s, sizeof(s)); // 从源文件中读
count += ret; // 将每次读的数据加到count中
write(fd2, s, ret);
}
// 4.关闭文件
close(fd1);
close(fd2);
return 0;
}
void* thread(void* arg)
{
file_t *f = (file_t *)arg;
copy_file(f->src, f->dest, f->start, f->len);
pthread_exit(NULL);
}

int main(int argc, const char* argv[])
{
int len;
pid_t pid;
// 1.检查参数个数
if (argc != 3) {
fprintf(stderr, "input error,try again\n");
fprintf(stderr, "usage:./a.out srcfile destfile\n");
return -1;
}
// 2.获取源文件大小
len = get_file_len(argv[1]);

// 3.创建出目标文件,并清空
init_src_file(argv[2]);

// 4.创建两个线程拷贝文件
pthread_t tid1, tid2;
file_t f[] = {
[0] = {
.src = argv[1],
.dest = argv[2],
.start = 0,
.len = len / 2,
},
[1] = {
.src = argv[1],
.dest = argv[2],
.start = len/2,
.len = (len - len / 2),
},
};
if ((errno = pthread_create(&tid1, NULL, thread, (void *)&f[0])))
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid2, NULL, thread, (void *)&f[1])))
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

pthread_detach 函数

线程的状态:结合态分离态
在使用 pthread_create 函数创建线程的时候默认就是结合态的线程,结合态的线程需要调用 pthread_join 来回收资源,如果将线程标记为分离态,分离态的线程资源会被自动回收,不需要其他的线程回收其资源。

1
2
3
4
5
int pthread_detach(pthread_t thread);
// 功能:将线程标记为分离态
// 参数:
// @thread:线程号
// 返回值:成功返回0,失败返回错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <head.h>

void* task1(void* arg)
{
int n = 5;
pthread_detach(pthread_self());
while (1) {
printf("我是子线程\n");
sleep(1);
n--;
if (n == 0)
pthread_exit(NULL);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid;

if ((errno = pthread_create(&tid, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

sleep(1);

pthread_join(tid,NULL);
printf("OOOOOOOOOOOOOOOOOO\n");
return 0;
}

pthread_cancel 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int pthread_cancel(pthread_t thread);
// 功能:给thread发送一个取消的信号
// 参数:
// @thread:线程号
// 返回值:成功返回0,失败返回错误码

int pthread_setcancelstate(int state, int *oldstate);
// 功能:设置线程是否可被取消
// 参数:
// @state:
// PTHREAD_CANCEL_ENABLE:线程可被取消(默认)
// PTHREAD_CANCEL_DISABLE:线程不能被取消
// @oldstate:旧的线程状态
// 返回值:成功返回0,失败返回错误码

int pthread_setcanceltype(int type, int *oldtype);
// 功能:设置线程取消时机
// 参数:
// @type:
// PTHREAD_CANCEL_DEFERRED:延时取消(默认),线程中如果是while(1),就找不到取消点,线程取消将被延时
// PTHREAD_CANCEL_ASYNCHRONOUS:立即取消
// @oldtype:旧的线程类型
// 返回值:成功返回0,失败返回错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <head.h>
pthread_t tid1, tid2;
void* task1(void* arg)
{
sleep(3);
pthread_cancel(tid2);
while (1);
}
void* task2(void* arg)
{
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
while (1) {
// printf("我是线程2...\n");
// sleep(1);
}
}
int main(int argc, const char* argv[])
{
if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

多线程访问全局变量问题

对于这个代码,如果 num 变量不加 volatile,使用gcc编译的使用如果加上 -O1 -O2 -O3 优化等级(数字越大优化等级越高)会发现 task2 线程不能够退出,因为编译器对 num 变量进行了优化认为 num 的值一直都是 0 ,所以出现了死循环,为了解决这个问题就需要在 num 变量前加 volatile,不让编译器对其优化,每次都能得到想要的结果。

总结:多线程访问全局变量的时候,对变量加 volatile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <head.h>
volatile int num = 0;
void* task1(void* arg)
{
sleep(2);
num = 1;
printf("线程1退出了...\n");
pthread_exit(NULL);
}
void* task2(void* arg)
{
while (num == 0);
printf("线程2退出了...\n");
pthread_exit(NULL);
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2;

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

线程间通信

线程间通信一般在指线程互斥机制和线程的同步机制。

多线程产生竞态的问题

多个线程同时使用同一个全局变量的时候,两个线程都想操作这个变量,此时竞态就会产生了,可以使用 互斥锁信号量条件变量等来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <head.h>
volatile int money = 10000;
// 张三
void* task1(void* arg)
{
while (1) {
money -= 50;
if (money >= 0) {
printf("张三成功取了50块钱,余额=%d\n", money);
} else {
money += 50;
printf("张三取钱失败,余额不足\n");
pthread_exit(NULL);
}
sleep(1);
}
}
// 李四
void* task2(void* arg)
{
while (1) {
money -= 100;
if (money >= 0) {
printf("李四成功取了100块钱,余额=%d\n", money);
} else {
money += 100;
printf("李四取钱失败,余额不足\n");
pthread_exit(NULL);
}
sleep(1);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2;

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

线程的互斥锁

线程互斥锁的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1.定义互斥锁
pthread_mutex_t mutex;

// 2.初始化线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 静态初始化
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
// 动态初始化
// 功能:初始化互斥锁
// 参数:
// @mutex:被初始化的锁
// @attr:锁的属性,一般填写为NULL(默认属性)
// 返回值:成功返回0,失败返回错误码

// 3.上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 尝试获取锁,如果锁资源存在那就占用锁,如果锁资源不可利用,立即返回。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 功能:上锁(如果线程获取不到锁的资源,线程阻塞,直到其他的线程将锁释放)
// 参数:
// @mutex:执行锁的指针
// 返回值:成功返回0,失败返回错误码

// 4.解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 功能:解锁
// 参数:
// @mutex:执行锁的指针
// 返回值:成功返回0,失败返回错误码

// 5.销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 功能:销毁互斥锁
// 参数:
// @mutex:执行锁的指针
// 返回值:成功返回0,失败返回错误码

线程互斥锁的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <head.h>
pthread_mutex_t lock; // 定义锁

volatile int money = 10000; //临界资源
// 张三
void* task1(void* arg)
{
while (1) {
pthread_mutex_lock(&lock);
money -= 50;
if (money >= 0) {
printf("张三成功取了50块钱,余额=%d\n", money);
} else {
money += 50;
printf("张三取钱失败,余额不足\n");
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
// sleep(1);
pthread_mutex_unlock(&lock);
}
}
// 李四
void* task2(void* arg)
{
while (1) {
pthread_mutex_lock(&lock);
money -= 100;
if (money >= 0) {
printf("李四成功取了100块钱,余额=%d\n", money);
} else {
money += 100;
printf("李四取钱失败,余额不足\n");
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
// sleep(1);
pthread_mutex_unlock(&lock);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2;

// 初始化互斥锁
pthread_mutex_init(&lock, NULL);

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);

// 销毁锁
pthread_mutex_destroy(&lock);
return 0;
}

线程互斥锁死锁问题

  1. 产生死锁的四个必要条件
    • 互斥
    • 请求保持
    • 不可剥夺
    • 循环等待
  2. 死锁的规避方法
    • 指定线程获取锁的顺序
    • 尽量避免锁的嵌套使用
    • 给线程上锁指定超时时间 pthread_mutex_timedlock
    • 在全局位置指定锁是否被使用的状态,如果被使用就不在获取

无名信号量

无名信号的工作原理

当多个线程在访问全局变量的时候如果使用互斥锁只能保证有一个线程在操作临界资源,不能保证线程的执行的先后顺序,如果想要控制线程的执行顺序就可以使用无名信号量完成,无名信号量是实现线程同步的机制。线程同步机制一般使用在生产者和消费者模型上。

无名信号量的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <semaphore.h>

// 1.定义无名信号量
sem_t sem;

// 2.初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 功能:初始化无名信号量
// 参数:
// @sem:指向无名信号量的指针
// @pshared:0 线程的同步
// 1 进程的同步(亲缘关系进程)
// @value:信号的初值 1
// 返回值:成功返回0,失败返回-1置位错误码

// 3.获取信号量(P操作)
int sem_wait(sem_t *sem);
// 功能:申请资源(让信号量的值减去1,然后和0比较如果结果为0,表示获取锁成功了)
// 如果在调用sem_wait的时候获取不到资源,sem_wait会阻塞
// 参数:
// @sem:指向无名信号量的指针
// 返回值:成功返回0,失败返回-1置位错误码

// 4.释放信号量(V操作)
int sem_post(sem_t *sem);
// 功能:释放资源
// 参数:
// @sem:指向无名信号量的指针
// 返回值:成功返回0,失败返回-1置位错误码

// 5.销毁无名信号量
int sem_destroy(sem_t *sem);
// 功能:销毁无名信号量
// 参数:
// @sem:指向无名信号量的指针
// 返回值:成功返回0,失败返回-1置位错误码

无名信号量实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <head.h>
sem_t sem1, sem2; // 定义无名信号量
// 生产者线程
void* task1(void* arg)
{
while (1) {
sem_wait(&sem1); //申请资源sem1
printf("我生成了一部手机...\n");
sem_post(&sem2); // 释放资源 sem2
}
}
// 消费者线程
void* task2(void* arg)
{
while (1) {
sem_wait(&sem2); // 申请资源 sem2
printf("我购买了一部手机\n");
sem_post(&sem1); //释放资源 sem1
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2;

// 初始化无名信号量
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);

// 销毁无名信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}

无名信号量的应用

有三个线程分别打印ABC,使用无名信号量实现三个线程同步,逐次打印 ABCABCABC...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <head.h>
sem_t sem1, sem2, sem3; // 定义无名信号量
// A
void* task1(void* arg)
{
while (1) {
sem_wait(&sem1); // 申请资源sem1
printf("A");
sem_post(&sem2); // 释放资源 sem2
}
}
// B
void* task2(void* arg)
{
while (1) {
sem_wait(&sem2); // 申请资源 sem2
printf("B");
sem_post(&sem3); // 释放资源 sem3
}
}
// C
void* task3(void* arg)
{
while (1) {
sem_wait(&sem3); // 申请资源 sem2
printf("C\n");
sleep(1);
sem_post(&sem1); // 释放资源 sem1
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2,tid3;

// 初始化无名信号量
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid3, NULL, task3, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);

// 销毁无名信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}

条件变量

条件变量的工作原理

条件变量也是线程的同步机制,条件变量更适合用在多个线程的同步工作。

条件变量的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1.定义条件变量
pthread_cond_t cond;

// 2.初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// //静态初始化
int pthread_cond_init(pthread_cond_t * cond,const pthread_condattr_t * attr);
// 功能:动态初始化一个条件变量
// 参数:
// @cond:条件变量的指针
// @attr:NULL使用默认属性
// 返回值:成功返回0,失败返回非0

// 3.阻塞等待条件变量
int pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);
// 功能:阻塞等待条件变量,在条件变量中维护了一个队列,这里的互斥锁就是为
// 了解决在往队列中放线程的时候出现竞态问题的。
// 参数:
// @cond:条件变量的地址
// @mutex:互斥锁
// 返回值:成功返回0,失败返回非零
// 使用的步骤:
// 1.使用pthread_mutex_lock上锁
// 2.调用pthread_cond_wait
// 2.1将当前线程放入队列
// 2.2解锁
// 2.3休眠
// 2.4获取锁
// 2.5休眠状态退出
// 3.你的程序
// 4.使用pthread_mutex_unlock解锁

// 4.给休眠的线程发信号或者广播
int pthread_cond_signal(pthread_cond_t *cond);
// 功能:唤醒(至少)一个休眠的线程
// 参数:
// @cond:条件变量的地址
// 返回值:成功返回0,失败返回非零
// int pthread_cond_broadcast(pthread_cond_t *cond);
// 功能:唤醒所有休眠的线程
// 参数:
// @cond:条件变量的地址
// 返回值:成功返回0,失败返回非零

// 5.销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
// 功能:销毁条件变量
// 参数:
// @cond:条件变量的地址
// 返回值:成功返回0,失败返回非零

条件变量的实例

一个生产者和多个消费者线程同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <head.h>
pthread_cond_t cond; //定义条件变量
pthread_mutex_t lock; //定义互斥锁
// 生成者
void* task1(void* arg)
{
while (1) {
sleep(1);
printf("我生产了一部手机\n");
// pthread_cond_signal(&cond); //释放资源
pthread_cond_broadcast(&cond);
}
}
// 消费者
void* task2(void* arg)
{
while (1) {
pthread_mutex_lock(&lock);
pthread_cond_wait(&cond,&lock);
printf("%#lx购买了一部手机\n",pthread_self());
pthread_mutex_unlock(&lock);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2, tid3, tid4, tid5;
pthread_mutex_init(&lock,NULL);//初始化互斥锁
pthread_cond_init(&cond,NULL); //初始化条件变量

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid3, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid4, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");
if ((errno = pthread_create(&tid5, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

printf("tid2=%#lx,tid3=%#lx,tid3=%#lx,tid5=%#lx\n",
tid2, tid3, tid4, tid5);

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_join(tid4, NULL);
pthread_join(tid5, NULL);

pthread_cond_destroy(&cond);
pthread_mutex_destroy(&lock);
return 0;
}

(一个)生产者线程和消费者线程同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <head.h>
pthread_cond_t cond; // 定义条件变量
pthread_mutex_t lock; // 定义互斥锁
int flags = 0; // 标志位变量

//情况1: B(pthread_cond_wait) A A(pthread_cond_wait) B
//情况2: B(pthread_cond_wait) A B
//情况3: A A(pthread_cond_wait) B
//情况4: A B

// 生成者 A
void* task1(void* arg)
{
while (1) {
pthread_mutex_lock(&lock);
if (flags == 1)
pthread_cond_wait(&cond, &lock);
printf("我生产了一部手机\n");
pthread_cond_signal(&cond); // 释放资源
flags = 1;
pthread_mutex_unlock(&lock);
}
}
// 消费者 B
void* task2(void* arg)
{
while (1) {
pthread_mutex_lock(&lock);
if (flags == 0)
pthread_cond_wait(&cond, &lock);
printf("购买了一部手机\n");
pthread_cond_signal(&cond); // 释放资源
flags = 0;
pthread_mutex_unlock(&lock);
}
}
int main(int argc, const char* argv[])
{
pthread_t tid1, tid2;
pthread_mutex_init(&lock, NULL); // 初始化互斥锁
pthread_cond_init(&cond, NULL); // 初始化条件变量

if ((errno = pthread_create(&tid1, NULL, task1, NULL)) != 0)
PRINT_ERR("pthread_create error");

if ((errno = pthread_create(&tid2, NULL, task2, NULL)) != 0)
PRINT_ERR("pthread_create error");

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);

pthread_cond_destroy(&cond);
pthread_mutex_destroy(&lock);
return 0;
}

条件变量使用场景

无名信号量适合在线程数比较少的线程中实现同步过程,而条件变量适合在大量线程实现同步过程。

例如你要编写一个12306买票的服务器当客户端访问服务器的时候,服务器会创建一个线程服务于这个用户。如果有多个用户同时想买票,此时服务需要在 瞬间创建 一堆线程,这个时间比较长,对用户的体验感不好。所以12306服务器是在启动的时候都已经创建好一堆线程。调用 pthread_cond_wait 让这些线程休眠,当有客户端请求买票的时候,只需要唤醒这些休眠的线程即可,由于省去了创建线程的时间,所以这种方式的效率非常的高。