18.2 线程创建及运行
18.2 线程创建及运行
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.| 参数 | 值 | 用途 | 说明 |
|---|---|---|---|
| thread | a successful call to pthread_create() stores the ID of the new thread in the buffer pointed to by thread | “保存新创建线程ID的变量地址值” | |
| attr | The attr argument points to a pthread_attr_t structure whose contents are used at thread creation time to determine attributes for the new thread; this structure is initialized using pthread_attr_init(3) and related functions. If attr is NULL, then the thread is created with default attributes. | “用于传递线程属性的参数,传递NULL时,创建默认属性的线程” | |
| start_routine | The new thread starts execution by invoking start_routine(); | “相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)” | |
| arg | arg is passed as the sole argument of start_routine() | 传递给start_routine表示的函数的参数 | |
| RETURN VALUE | On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread * are undefined. |
在编译时需要加上 -pthread 参数
问:start_routine只有一个类型为 void*的参数,所以不能将带有多个参数的函数作为子线程?
线程终止的情况
The new thread terminates in one of the following ways:
- It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls pthread_join(3).
- It returns from start_routine(). This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
- It is canceled (see pthread_cancel(3)).
- Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.
- 线程自身返回或结束:调用pthread_exit 或 return
- 通过pthread_cancel send a cancellation request to a thread(线程ID )
- 线程所在的进程终止:进程内任意线程调用exit(); 或 主线程-main 函数所在线程 返回
线程的创建和执行流程
创建一个子线程并在子线程中进程打印
实现:
修改CMake
# 查找线程库
find_package(Threads REQUIRED)
# 链接到目标
target_link_libraries(Network PRIVATE Threads::Threads)//...
#include <pthread.h>
void* thread_main(void* arg)
{
int i;
int cnt = *(int*)arg;
for(i=0; i<cnt; i++)
{
sleep(1);
printf("thread %ld: %d\n", pthread_self(), i);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
int param = 5;
if(pthread_create(&tid, NULL, thread_main, (void*)¶m) != 0)
error_handling("pthread_create() error");
printf("tid:%ld\n", tid);
sleep(7);
printf("main thread end\n");
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 3-13 | 要执行的代码流 - 线程函数 | |
| 6 | 将接收的void *类型转换为实际传入的类型 | |
| 9 | 在子线程中休眠1s | |
| 10 | 打印线程id 以及 本地变量 | |
| 17 | 保存新建线程的id | |
| 18 | 传给线程函数的参数 | |
| 19 | 创建子线程 | |
| 22 | 打印子线程的id |
效果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network
tid:140393248913152
thread 140393248913152: 0
thread 140393248913152: 1
thread 140393248913152: 2
thread 140393248913152: 3
thread 140393248913152: 4
main thread end线程的终止
如果将main中的sleep(7)注释掉,执行的结果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network
tid:140016003086080
main thread end由于main函数返回,子线程还没有执行就已经终止
等待线程的终止
NAME
pthread_join - join with a terminated thread
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);| 参数 | 值 | 用途 | 说明 |
|---|---|---|---|
| thread | waits for the thread specified by thread to terminate | ||
| retval | If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that the target thread supplied to pthread_exit(3)) into the location pointed to by retval. | “保存线程函数返回值的指针变量地址值“ | |
| RETURN VALUE | On success, pthread_join() returns 0; on error, it returns an error number. |
调用pthread_join() 的线程阻塞等待指定线程终止,并获取线程函数的执行结果 - 返回值。
问:为什么获取返回值的参数类型是void**,而不是 void*?
首先,要修改参数的值,需要传递指针(变量的地址)。
其次,线程函数返回的参数类型是void*,实参的类型应该也是void*。
所以修改实参void*的值应该传递其地址。
void* 表示通用指针,指向的可以是对象,也可以是指针。拷贝的时候不去管实际的类型,使用的时候再进行类型转换。
不能对void* 进行解引用,因为void - 空、通用 没有具体的长度。
测试
实现:
void* thread_main(void* arg)
{
//...
char *msg = (char*)malloc(sizeof(char));
*msg = 'A';
return (void*)msg;
}
int main(int argc, char* argv[])
{
//...
char *ret = NULL;
pthread_join(tid, (void**)&ret);
printf("thread return message: %c\n", *ret);
free(ret);
printf("main thread end\n");
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 4-6 | 在子线程中申请堆内存 并 赋值 | |
| 13 | 等待子线程结束 并 获取返回值 | |
| 15 | 释放申请的堆内存 |
效果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network
tid:140387306407680
thread 140387306407680: 0
thread 140387306407680: 1
thread 140387306407680: 2
thread 140387306407680: 3
thread 140387306407680: 4
thread return message: A
main thread end可在临界区内调用的函数
临界区
相关信息
多个线程同时调用函数(执行时)可能产生问题 (的区域 - 代码)
- 线程安全函数(Thread-safe-function):被多个线程同时调用不会产生问题
- 非线程安全函数(Thread-unsafe-function):会引发问题
注
临界区和线程是否安全无关。线程安全函数同样可能存在临界区。只是在线程安全函数中,有措施避免问题。
我的理解:两者关注的范围不一样。临界区关注的是代码块 - 是否存在能产生问题的区域。线程是否安全关注的是整个线程函数的调用是否存在问题。
- 大多是标准函数是线程安全的
- 同时提供完全相同功能的线程安全 和 非线程安全函数,如:gethostbyname 、gethostbyname_r。线程安全函数通常以 _r 作为后缀
- 可以通过定义 _REENTRANT 宏自动将非线程安全函数替换为线程安全函数
- 可以在编译时添加 -D_REENTRANT 选项完成替换
工作线程模型
使用多线程计算110的和,线程1计算15的和,线程2计算6~10的和。
实现:
int sum = 0;
void* thread_summation(void *arg)
{
int start = ((int*)arg)[0];
int end = ((int*)arg)[1];
while(start <= end)
{
sum += start;
start++;
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid0, tid1;
int range0[] = {1, 5};
int range1[] = {6, 10};
if(pthread_create(&tid0, NULL, thread_summation, (void*)range0) != 0)
error_handling("pthread_create() error");
if(pthread_create(&tid1, NULL, thread_summation, (void*)range1) != 0)
error_handling("pthread_create() error");
pthread_join(tid0, NULL);
pthread_join(tid1, NULL);
printf("sum: %d\n", sum);
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 1 | 定义存储结果的变量 | 全局变量,存储在数据区,线程之间共享 |
| 3-13 | 计算和 |
效果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network
sum: 55结果正确
临界区
这里两个线程都会访问全局变量 sum,对sum的操作( sum += start)就是临界区
非线程安全
创建两个线程,一个增加全局变量的值,另一个减少全局变量的值。
long long num = 0;
void* thread_inc(void* arg)
{
long i;
for(i=0; i<5000000000; i++)
num++;
return NULL;
}
void* thread_dec(void* arg)
{
long i;
for(i=0; i<5000000000; i++)
num--;
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid0, tid1;
int range0[] = {1, 10000};
int range1[] = {10001, 20000};
if(pthread_create(&tid0, NULL, thread_inc, NULL) != 0)
error_handling("pthread_create() error");
if(pthread_create(&tid1, NULL, thread_dec, NULL) != 0)
error_handling("pthread_create() error");
pthread_join(tid0, NULL);
pthread_join(tid1, NULL);
printf("num: %lld\n", num);
return 0;
}inc增加num的值,des减少num的值,两个线程增减的数值(程度)相同,理论上最后的结果应该是0
结果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network
num: 5034843
ming@ubuntu:/media/sf_share/Network/build$ ./Network
num: 3980714实际运行结果并不为0,且每次运行的结果都不相同