Linux多线程编程(2)——pthread实践

前言

之前了解一些pthread的知识,但不够详细,进行一些简单的实践。

打印线程id

代码:

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_t ntid;
void
printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
}
void *
thr_fn(void *arg)
{
printids("New thread: ");
return ((void *)0);
}
int
main(void)
{
int err;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
{
printf("can't create thread: %s\n", strerror(err));
return -1;
}
printids("main thread: ");
sleep(1);
return 0;
}

在Mac(FreeBSD)结果:

1
2
3
$ ./print_tid
main thread: pid 87534 tid 1952662288 (0x74634310)
New thread: pid 87534 tid 137158656 (0x82ce000)

在Linux结果:

1
2
3
$ ./print_id
main thread: pid 1205 tid 1283360576 (0x4c7e8740)
New thread: pid 1205 tid 1275123456 (0x4c00d700)
  • 这里主线程需要休眠以等待新线程有机会运行,否则新线程还没有运行,而整个进程已经结束了。
  • 主线程的输出基本上出现在新建线程的输出之前,但 =不能在线程调度上做出任何假设=

获取线程退出状态

代码:

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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *
thr_fn1(void *arg)
{
printf("thread 1 returning\n");
return ((void *)1);
}
void *
thr_fn2(void *arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if (err != 0)
{
printf("can't create thread 1: %s\n", strerror(err));
return -1;
}
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
{
printf("can't create thread 2: %s\n", strerror(err));
return -1;
}
err = pthread_join(tid1, &tret);
if (err != 0)
{
printf("can't join with thread 1: %s\n", strerror(err));
return -1;
}
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
{
printf("can't join with thread 2: %s\n", strerror(err));
return -1;
}
printf("thread 2 exit code %d\n", (int)tret);
return 0;
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./tid_status
./tid_status
thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2
$ ./tid_status
./tid_status
thread 2 exiting
thread 1 returning
thread 1 exit code 1
thread 2 exit code 2
  • 线程运行顺序不可假设
  • 通过 =pthread_join= 可以获得线程的退出状态

pthread_exit参数的错误使用

代码:

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
struct foo {
int a, b, c, d;
};
void
printfoo(const char *s, const struct foo * fp)
{
printf(s);
printf(" structure at 0x%x\n", (unsigned)fp);
printf(" foo.a = %d\n", fp->a);
printf(" foo.b = %d\n", fp->b);
printf(" foo.c = %d\n", fp->c);
printf(" foo.d = %d\n", fp->d);
}
void *
thr_fn1(void *arg)
{
struct foo foo = { 1, 2, 3, 4};
printfoo("thread 1: \n", &foo);
pthread_exit((void *)&foo); // 局部变量,不应该返回
}
void *
thr_fn2(void *arg)
{
printf("thread 2: ID is %d\n", pthread_self());
pthread_exit((void *)0);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
struct foo *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if (err != 0)
{
printf("can't create thread 1: %s\n", strerror(err));
return -1;
}
err = pthread_join(tid1, (void *)&fp); // 返回的结构内存被收回了
if (err != 0)
{
printf("can't join with thread 1: %s\n", strerror(err));
return -1;
}
sleep(1);
printf("parent starting second thread\n");
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
{
printf("can't create thread 2: %s\n", strerror(err));
return -1;
}
sleep(1);
printfoo("parent: \n", fp);
return 0;
}

Linux运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./pthread_exit
thread 1:
structure at 0xd686cf00
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
parent starting second thread
thread 2: ID is -695806208
parent:
structure at 0xd686cf00
foo.a = -695806208
foo.b = 32650
foo.c = -695806208
foo.d = 32650

Mac运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
$ ./pthread_exit
thread 1:
structure at 0x912ed0
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
parent starting second thread
thread 2: ID is 9515008
parent:
structure at 0x912ed0
Segmentation fault: 11
  • pthread_create, pthread_exit的参数可以是包含更加复杂信息结构的地址
  • 要保证这个数据结构所使用的 =内存在调用者完成以后仍然是有效= 的,否则就会出现无效或非法内存访问
  • 解决这个问题,可以使用 =全局结构= 或者 =用malloc分配结构=

线程清理处理程序

代码:

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void
cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *
thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1 first handler");
pthread_cleanup_push(cleanup, "thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
{
return ((void *)1);
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return ((void *)1);
}
void *
thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)
{
pthread_exit((void *)2);
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void *)0);
if (err != 0)
{
printf("can't create thread 1: %s\n", strerror(err));
return -1;
}
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
{
printf("can't create thread 2: %s\n", strerror(err));
return -1;
}
err = pthread_join(tid1, &tret);
if (err != 0)
{
printf("can't join with thread 1: %s\n", strerror(err));
return -1;
}
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
{
printf("can't join with thread 2: %s\n", strerror(err));
return -1;
}
printf("thread 2 exit code %d\n", (int)tret);
return 0;
}

Linux运行结果:

1
2
3
4
5
6
7
8
9
$ ./pthread_cleanup
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 exit code 2

Mac运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ ./pthread_cleanup
thread 1 start
thread 2 start
thread 1 push complete
thread 2 push complete
Bus error: 10
$ lldb
...
thread 2 start
thread 1 start
thread 2 push complete
thread 1 push complete
cleanup: thread 2 second handler
Process 19485 stopped
* thread #2: tid = 0x3ba0fc, 0x00000001000b6000, stop reason = EXC_BAD_ACCESS (code=2, address=0x1000b6000)
frame #0: 0x00000001000b6000
-> 0x1000b6000: pushq %rdx
0x1000b6002: pushq %rsp
0x1000b6004: addb %al, (%rax)
0x1000b6006: addb %al, (%rax)

对于Mac这个问题,在 pthread_cleanup_pop with argument 0?找答案:

1
2
3
4
5
6
7
8
9
10
11
The reason your code executed cleanup handlers, is that your control never reaches pthread_cleanup_pop(0). Instead, you always execute return in thr_fn1, and pthread_exit in thr_fn2.
Try creating the threads with pthread_create(&tid1, NULL, thr_fn1, (void *)0); instead of 1. When I do that, I get (expected):
thread 1 start
thread 1 push complete
thread 2 start
thread 2 push complete
thread 1 exit code 1
thread 2 exit code 2
`

为让thr_fn1函数中能运行 =pthread_cleanup_pop= ,最终代码改成:

1
2
3
4
5
//...
err = pthread_create(&tid1, NULL, thr_fn1, (void *)0);
//...
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
//...
  • 只有第二个线程的清理程序调用了,因为第一个线程终止方式是 =直接return返回= ,而不是 =pthread_exit=
  • 清理程序时按照注册的相反顺序执行,即栈的 =后入先出=

使用互斥量保护数据结构

代码:

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
struct foo {
int f_count;
pthread_mutex_t f_lock;
};
struct foo *
foo_alloc(void)
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL)
{
fp->f_count = 1;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
{
free(fp);
return (NULL);
}
}
return (fp);
}
void
foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void
foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0)
{
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destory(&fp->f_lock);
free(fp);
}
else
{
pthread_mutex_unlock(&fp->f_lock);
}
}

后记

线程同步:

  • 互斥量
  • 读写锁
  • 条件变量

资料

吴羽舒 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!