Linux 《Linux多线程编程基础入门》实验报告

6361642989056_.pic.jpg

操作线程的函数

  • 线程的创建
1
2
3
4
5
6
7
# include <pthread.h>
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg
);
参数 说明
thread 输出参数,由 pthread_create 在线程创建成功后返回的线程句柄,该句柄在后续操作线程的 API 中用于标志该新建的线程;
start_routine 输入参数,新建线程的入口函数;
arg 输入参数,传递给新线程入口函数的参数;
attr 输入参数,指定新建线程的属性,如线程栈大小等;如果值为 NULL,表示使用系统默认属性。
  • 线程ID
1
2
include <pthread.h>
pthread_t pthread_self(void);
  • 判断两个给定的线程ID是否相等
1
2
3
4
5
include <pthread.h>
int pthread_equal(
pthread_t t1,
pthread_t t2
);
参数 说明
t1 线程ID t1
t2 线程ID t2
  • 线程的终止
1
2
include <pthread.h>
void pthread_exit(void *retval);
参数 说明
retval 输出参数,等待的线程终止时的返回值
  • 线程的连接
1
2
3
4
5
include <pthread.h>
int pthread_join(
pthread_t thread,
void **retval
);
参数 说明
thread 输入参数,指定希望等待的线程
retval 输出参数,等待的线程终止时的返回值,就是在线程入口函数中 return 的值或者调用 pthread_exit 函数的参数
  • 线程的分离
1
2
# include <pthread.h>
int pthread_detach(pthread_t thread);
参数 说明
thread 输入参数,指定希望执行分离操作的线程

线程的属性

  • 属性的初始化与销毁
1
2
3
# include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
  • 获取指定线程的属性
1
2
3
4
5
6
# define _GNU_SOURCE
# include <pthread.h>
int pthread_getattr_np(
pthread_t thread,
pthread_attr_t *attr
);
  • 设置和获取线程的分离状态
1
2
3
4
5
6
7
8
9
# include <pthread.h>
int pthread_attr_setdetachstate(
pthread_attr_t *attr,
int detachstate
);
int pthread_attr_getdetachstate(
pthread_attr_t *attr,
int *detachstate
);
  • 设置和获取线程栈地址及大小
1
2
3
4
5
6
7
8
9
10
11
# include <pthread.h>
int pthread_attr_setstack(
pthread_attr_t *attr,
void *stackaddr,
size_t stacksize
);
int pthread_attr_getstack(
pthread_attr_t *attr,
void **stackaddr,
size_t *stacksize
);

线程的一次性初始化及特有数据

场景:我们同时创建了N个线程,无法知道哪个线程会被先执行。我们期望这N个线程中谁先运行谁就要去负责去调用一个全局的初始化函数做相关的初始化动作,而该初始化函数仅能被调用一次。

  • 一次性初始化线程
1
2
3
4
5
# include <pthread.h>
int pthread_once(
pthread_once_t *once_control,
void (*init)(void)
);
参数 说明
once_control 输入参数,指针,指向通过以下方式初始化的全局变量 pthread_once_t once_var = PTHREAD_ONCE_INIT;
init 输入参数,函数指针,执行仅能被执行一次的初始化函数
  • 线程特有数据
1
2
3
4
5
6
7
8
9
10
# include <pthread.h>
int pthread_key_create(
pthread_key_t *key,
void (*destructor)(void *)
);
int pthread_setspecific(
pthread_key_t key,
const void *value
);
void *pthread_getspecific(pthread_key_t key);
参数 说明
pthread_key_create 创建一个全局唯一的 key,用于表示一个数据概念
pthread_setspecific 用于线程给某个数据概念分配内存
pthread_getspecific 用于线程针对某个数据概念获取其对应的内存(每个线程获取的内存是不一样的),如果函数返回 NULL 值说明线程还未对该数据概念分配内存
  • 创建 mystrerror.c,代码如下:
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
# define _GNU_SOURCE
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <errno.h>
# include <pthread.h>

# 代码思路
# 先用 pthread_key_create 创建一个全局的 key,用于表示一块全局的数据概念。
# 每个线程在使用该数据概念时,先通过 pthread_getspecific 查询该线程是否为该数据概念分配了内存
# 如果线程未对该数据概念分配内存,使用 pthread_setspecific 为该数据概念分配特有内存
# 如果线程已对该数据概念分配内存,直接操作该内存

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

# define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
# define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

# define MAX_ERROR_LEN 256

/*线程特有数据的析构函数*/
static void destructor(void *buf)
{
free(buf);
}


static void createKey(void)
{
int s;
/*在phtread_once函数里创建特有数据的key,
哪个线程先调用就哪个线程创建key*/
s = pthread_key_create(&strerrorKey, destructor);
if (s != 0)
handle_error_en(s, "pthread_key_create");
}

char *mystrerror(int err)
{
int s;
char *buf;

/*一次性初始化函数*/
s = pthread_once(&once, createKey);
if (s != 0)
handle_error_en(s, "pthread_once");

/*获取线程特有数据*/
buf = pthread_getspecific(strerrorKey);
if (buf == NULL) { /*第一次获取为NULL, 线程需要分配内存*/
buf = malloc(MAX_ERROR_LEN);
if (buf == NULL)
handle_error("malloc");
/*设置特有数据内存*/
s = pthread_setspecific(strerrorKey, buf);
if (s != 0)
handle_error_en(s, "pthread_setspecific");
}

if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
}

return buf;
}

线程的取消

场景:一个线程可以对另一个线程提出取消申请,即线程被动终止的一种情况。

1
2
# include <pthread.h>
int pthread_cancel(pthread_t thread);
  • 创建 thread_cancel_sample.c,代码如下:
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
/*
* file name:thread_cancel_sample.c
*/

# include <stdio.h>
# include <stdlib.h>
# include <pthread.h>
# include <errno.h>
# include <unistd.h>

# define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

static void *thread_routine(void *arg)
{
int j;

printf("New thread started\n"); /*这里有可能是一个线程取消点 */
for (j = 1; ; j++) {
printf("Loop %d\n", j); /*这里也可能是一个线程取消点*/
sleep(1); /*这里是一个线程取消点*/
}
/* 线程执行流程无法到达此处 */
return NULL;
}

int main(int argc, char *argv[])
{
pthread_t thr;
int s;
void *res;
/*创建新线程*/
s = pthread_create(&thr, NULL, thread_routine, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");

/*让新线程飞一会儿*/
sleep(3);

/*取消新建线程*/
s = pthread_cancel(thr);
if (s != 0)
handle_error_en(s, "pthread_cancel");
/*等待新线程退出*/
s = pthread_join(thr, &res);
if (s != 0)
handle_error_en(s, "pthread_join");
/*判断新线程终止是否是响应取消请求而终止的*/
if (res == PTHREAD_CANCELED)
printf("Thread was canceled\n");
else
printf("Thread was not canceled (should not happen!)\n");

return 0;
}

线程互斥

场景:当有多个线程并发的在临界区执行时,程序的执行结果会出现不确定性,这种情况称之为竞态条件。多线程编程中,避免出现竞态条件的一项重要解决方案就是,保证多个线程在临界区是互斥的。所谓的互斥,就是指不能同时有多于一个线程进入临界区。保证临界区互斥的重要技术,就是互斥锁。

  • 互斥锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#  静态初始化一个全局的互斥锁
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

# 动态分配一个互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

# 释放动态分配的互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

# 持有互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

# 释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 创建 thread_sync_sample.c,代码如下:
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
/*
* file name: thread_sync_sample.c
*/

# include <stdio.h>
# include <pthread.h>
# include <stdlib.h>
# include <errno.h>

# define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
/*glob为多线程共享资源*/
static int glob = 0;
/* 静态初始化一个全局的互斥锁 */
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;


/*子线程访问修改共享资源glob*/
static void *thread_routine(void *arg)
{
int loc, j;
for (j = 0; j < 10000000; j++) {
/*进入临界区前,先持有互斥锁*/
pthread_mutex_lock(&mtx);
loc = glob;
loc++;
glob = loc;
/*退出临界区后,释放互斥锁*/
pthread_mutex_unlock(&mtx);
}
return NULL;
}

int main(int argc, char *argv[])
{
pthread_t t1, t2;
int s;

/*创建两个线程并发访问修改共享资源glob*/
s = pthread_create(&t1, NULL, thread_routine, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");
s = pthread_create(&t2, NULL, thread_routine, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");

/*等待子线程退出*/
s = pthread_join(t1, NULL);
if (s != 0)
handle_error_en(s, "pthread_join");
s = pthread_join(t2, NULL);
if (s != 0)
handle_error_en(s, "pthread_join");
/*输出结果*/
printf("glob = %d\n", glob);
exit(EXIT_SUCCESS);
}

线程同步

场景:我们有一个共享内存数据资源 M,我们整个程序设计需求是要求线程 A 在 M 上做了处理之后,线程 B 才能做处理。这种需要确保多线程间执行先后顺序的技术,称为线程的同步。条件变量是线程同步的主要手段。其大致的实现思想就是:线程 B,调用条件变量的接口让自身阻塞;线程 A,在处理完资源后,通过条件变量接口唤醒正在等待该资源的线程 B。

  • 条件变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#  静态初始化:与互斥锁类似静态初始化一个全局的条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

# 动态初始化:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

# 等待一个指定的条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

# 唤醒一个等待该条件变量的线程
int pthread_cond_signal(pthread_cond_t *cond);

# 唤醒所有等待该条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 创建 pthread_cond_sample.c,代码如下:
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 <pthread.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
# include <stdlib.h>
# include <stdint.h>


pthread_t threads[2];
char writer_char[2] = {'A', 'B'};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;


struct file_res{
pthread_t *writer; /*当前文件可以被哪个线程写入*/
int fd; /*文件描述符*/
}file_res =
{
.writer=&threads[0],/*file初始化可以被线程1写入'A'*/
};

/*线程1和线程2的入口函数*/
void *writer_routine(void *arg)
{
int index = (intptr_t)arg;
int i = 0;
int next_index=0;
printf("thread %d is running, and will write '%c' to file\n", index, writer_char[index]);

while(1)
{
if (0!=pthread_mutex_lock(&mutex))
exit(-1);
for(;;) {

/*如果当前线程可写file, 执行写操作*/
if (&threads[index]==file_res.writer) {
write(file_res.fd, &writer_char[index],
sizeof(writer_char[index]));

/*更新下一个可写线程*/
next_index = (index+1)%2;
file_res.writer = &threads[next_index];

/*执行写操作后,break for循环通过条件变量通知其他线程写*/
break;
}

/*当前线程不可写,等待其他线程唤醒*/
pthread_cond_wait(&cond,&mutex);
}

if (0!=pthread_mutex_unlock(&mutex))
exit(-1);

/*唤醒下一个线程*/
pthread_cond_signal(&cond);

}
}

int main(int argc, char* argv[])
{

/*创建空文件file*/
char file_name[] = "file";
if ((file_res.fd = open(file_name, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0)
{
printf("open %s error.\n", file_name);
exit(-1);
}

/*创建线程1和线程2*/
int i;
for (i=0; i<(sizeof(threads)/sizeof(pthread_t)); i++)
{
if(pthread_create(&threads[i], NULL, writer_routine, (void *)(intptr_t)i))
{
printf("create writer thread error\n");
exit(-1);
}
}

/*主线程退出*/
pthread_exit(NULL);
}
-------------本文结束感谢您的阅读-------------
0%