【图书推荐】《Linux C与C++一线开发实践(第2版)》_linux c与c++一线开发实践pdf-CSDN博客
《Linux C与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)
在POSIX API中,创建线程的函数是pthread_create,该函数声明如下:
int pthread_create(pthread_t *pid, const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
其中,参数pid是一个指针,指向创建成功后的线程的ID,pthread_t其实就是unsigned long int;attr是指向线程属性结构pthread_attr_t的指针,如果为NULL,则使用默认属性;start_routine指向线程函数的地址,线程函数就是线程创建后要执行的函数;arg指向传给线程函数的参数,如果执行成功,函数返回0。
创建完子线程后,主线程会继续执行后面的代码,这就可能会出现创建的子线程还没执行完,主线程就结束了,比如控制台程序,而主线程结束就意味着进程结束了。在这种情况下,我们需要等待子线程全部运行结束后再继续执行主线程。还有一种情况,主线程为了统计各个子线程工作的结果而需要等待子线程结束后再继续执行。POSIX提供了函数pthread_join来等待子线程结束,即子线程的线程函数执行完毕后,pthread_join才返回,因此pthread_join是一个阻塞函数。函数pthread_join会让主线程挂起(休眠,就是让出CPU),直到子线程都退出,同时pthread_join能让子线程所占资源得到释放。子线程退出后,主线程会接收到系统的信号,从休眠中恢复。函数pthread_join声明如下:
int pthread_join(pthread_t pid, void **value_ptr);
其中,参数pid是所等待线程的ID;value_ptr通常可设为NULL,如果不为NULL,则pthread_join 复制一份线程退出值到一个内存区域,并让*value_ptr指向该内存区域,因此pthread_join还有一个重要功能就是能获得子线程的返回值(这一点后面会讲到)。如果函数执行成功就返回0,否则返回错误码。
下面来实践一下,看几个简单的例子。
【例8.1】创建一个简单的线程,不传参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h> // 休眠
void *thfunc(void *arg) // 线程函数
{
printf("in thfunc\n");
return (void *)0;
}
int main(int argc, char *argv [])
{
pthread_t tidp;
int ret;
ret = pthread_create(&tidp, NULL, thfunc, NULL); // 创建线程
if (ret)
{
printf("pthread_create failed:%d\n", ret);
return -1;
}
sleep(1); // main线程挂起1秒钟,为了让子线程有机会执行
printf("in main:thread is created\n");
return 0;
}
(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:
# g++ -o test test.cpp -lpthread
# ./test
in thfunc
in main:thread is created
#
在这个例子中,首先创建一个线程,在线程函数中打印一行字符串后结束;而主线程在创建子线程后,会等待1秒,这样不至于因为主线程过早结束而导致进程结束。如果没有等待函数sleep,则可能子线程的线程函数还没来得及执行,主线程就结束了,这样会导致子线程的线程函数没有机会执行,因为主线程已经结束,整个应用程序已经退出了。
【例8.2】创建一个线程,并传入整型参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:
#include <pthread.h>
#include <stdio.h>
void *thfunc(void *arg)
{
int *pn = (int*)(arg); // 获取参数的地址
int n = *pn;
printf("in thfunc:n=%d\n", n);
return (void *)0;
}
int main(int argc, char *argv [])
{
pthread_t tidp;
int ret, n=110;
ret = pthread_create(&tidp, NULL, thfunc, &n); // 创建线程并传递n的地址
if (ret)
{
printf("pthread_create failed:%d\n", ret);
return -1;
}
pthread_join(tidp,NULL); // 等待子线程结束
printf("in main:thread is created\n");
return 0;
}
(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:
# g++ -o test test.cpp -lpthread
# ./test
in thfunc:n=110
in main:thread is created
#
这个例子和上面的例子有两点不同:一是创建线程的时候,把一个整型变量的地址作为参数传给线程函数;二是等待子线程结束没有用sleep函数,而是用pthread_join函数。sleep只是等待一个固定的时间,有可能在这个固定的时间内子线程早已经结束,或者子线程运行的时间大于这个固定时间,因此用它来等待子线程结束并不精确,而用pthread_join函数则会一直等到子线程结束后才执行该函数后面的代码,它的第一个参数就是子线程的ID。
【例8.3】创建一个线程,并传递字符串作为参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:
#include <pthread.h>
#include <stdio.h>
void *thfunc(void *arg)
{
char *str;
str = (char *)arg; // 得到传进来的字符串
printf("in thfunc:str=%s\n", str); // 打印字符串
return (void *)0;
}
int main(int argc, char *argv [])
{
pthread_t tidp;
int ret;
const char *str = "hello world";
ret = pthread_create(&tidp, NULL, thfunc, (void *)str);// 创建线程并传递str
if (ret)
{
printf("pthread_create failed:%d\n", ret);
return -1;
}
pthread_join(tidp, NULL); // 等待子线程结束
printf("in main:thread is created\n");
return 0;
}
(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:
# g++ -o test test.cpp -lpthread
# ./test
in thfunc:n=110,str=hello world
in main:thread is created
#
【例8.4】创建一个线程,并传递结构体作为参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:
#include <pthread.h>
#include <stdio.h>
typedef struct // 定义结构体的类型
{
int n;
char *str;
}MYSTRUCT;
void *thfunc(void *arg)
{
MYSTRUCT *p = (MYSTRUCT*)arg;
printf("in thfunc:n=%d,str=%s\n", p->n,p->str); // 打印结构体的内容
return (void *)0;
}
int main(int argc, char *argv [])
{
pthread_t tidp;
int ret;
MYSTRUCT mystruct; // 定义结构体
// 初始化结构体
mystruct.n = 110;
mystruct.str = "hello world";
ret = pthread_create(&tidp, NULL, thfunc, (void *)&mystruct);
// 创建线程并传递结构体地址
if (ret)
{
printf("pthread_create failed:%d\n", ret);
return -1;
}
pthread_join(tidp, NULL); // 等待子线程结束
printf("in main:thread is created\n");
return 0;
}
(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:
-bash-4.2# g++ -o test test.cpp -lpthread
-bash-4.2# ./test
in thfunc:n=110,str=hello world
in main:thread is created
-bash-4.2#
【例8.5】创建一个线程,共享进程数据
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:
#include <pthread.h>
#include <stdio.h>
int gn = 10; // 定义一个全局变量,将会在主线程和子线程中用到
void *thfunc(void *arg)
{
gn++; // 递增1
printf("in thfunc:gn=%d,\n", gn); // 打印全局变量gn值
return (void *)0;
}
int main(int argc, char *argv [])
{
pthread_t tidp;
int ret;
ret = pthread_create(&tidp, NULL, thfunc, NULL);
if (ret)
{
printf("pthread_create failed:%d\n", ret);
return -1;
}
pthread_join(tidp, NULL); // 等待子线程结束
gn++; // 子线程结束后,gn再递增1
printf("in main:gn=%d\n", gn); // 再次打印全局变量gn值
return 0;
}
(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:
-bash-4.2# g++ -o test test.cpp -lpthread
-bash-4.2# ./test
in thfunc:gn=11,
in main:gn=12
-bash-4.2#
从上例中可以看到,全局变量gn首先在子线程中递增1,子线程结束后,再在主线程中递增1。两个线程都对同一个全局变量进行了访问。