目录
1. 创建线程
功能:创建一个新的线程
参数
- thread:返回线程ID
- attr:设置线程的属性,attr为NULL表示使用默认属性
- tart_routine:是个函数地址,线程启动后要执行的函数
- rg:传给线程启动函数的参数
- 返回值:成功返回0;失败返回错误码
【错误检查】
Makefile
testThread:testThread.cc
g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f testThread
testThread.cc
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
int gcnt = 100;
// 新线程
void *ThreadRoutine(void *arg)
{
const char *threadname = (const char *)arg;
while (true)
{
std::cout << "I am a new thread: " << threadname << ", pid: " << getpid()<< std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");
while (true)
{
std::cout << "I am main thread"<< std::endl;
sleep(1);
}
return 0;
}
运行结果:
可以看到确实是有了两个线程都在运行,这里的线程就是一个是我们的main主线程,另一个则是我们新创建的线程。
接下来我们来认识一下POSIX线程库
2. POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
POSIX线程库,也称为pthread,是一种跨平台的标准API,它为应用程序提供了一套统一的方式来创建和管理线程。
-
由于Unix-like操作系统(如Linux、macOS)的广泛使用,POSIX标准允许开发者编写一次代码就能在多种平台上运行,避免了为每种操作系统单独编写和维护线程代码的工作量。
-
POSIX线程使得程序可以将并发处理的部分封装成独立的模块,便于模块间的通信和协同工作,提高代码的灵活性和效率。
-
标准化的线程API保证了不同系统之间的线程行为基本一致,有利于开发者理解和预测其行为,同时也有助于优化线程管理,提升系统的整体性能。
-
POSIX线程库提供了丰富的错误处理机制和同步原语(如互斥锁、条件变量),有助于开发者设计出健壮且安全的多线程应用。
-
虽然直接操作底层硬件线程可能会更复杂,但是通过POSIX API,开发者能以相对简单的方式实现复杂的并发控制,降低学习曲线。
开发者将pthread线程库开发成这样就是为了线程安全并且实现跨平台的性能,让OS能通过线程库提供API给用户才能实现线程的操作
另外需要注意的是,在linux中,如何对这些线程进行管理呢?
3. 线程ID及进程地址空间布局
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,
- 属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
// 新线程
void *ThreadRoutine(void *arg)
{
const char *threadname = (const char *)arg;
while (true)
{
std::cout << "I am a new thread: " << threadname << "thread id: " << pthread_self() << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");
while (true)
{
std::cout << "I am main thread" << "thread id: " << pthread_self()<< std::endl;
sleep(1);
}
return 0;
}
可以看到main线程id和新线程id是不一样的
pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址
上图左边是进程创建的地址空间,中间的mmap区域就是我们的共享区,里面便是Pthread库,右边就是Pthread库里有两个线程结构体图
4. 线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
4.1 pthread_exit()
线程终止函数接口
参数
- value_ptr : value_ptr不要指向一个局部变量。
- 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
测试代码
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
// 新线程
void *ThreadRoutine(void *arg)
{
const char *threadname = (const char *)arg;
int cnt = 5;
while (cnt--)
{
std::cout << "I am a new thread: " << threadname << std::endl;
sleep(1);
}
pthread_exit(nullptr);
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");
while (true)
{
std::cout << "I am main thread" << std::endl;
sleep(1);
}
return 0;
}
可以看到我们的主线程还在执行输出语句,新线程已经结束生命周期了
4.2 pthread_cancel()
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
// 新线程
void *ThreadRoutine(void *arg)
{
const char *threadname = (const char *)arg;
while (1)
{
std::cout << "I am a new thread: " << threadname << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");
sleep(5);
while (true)
{
std::cout << "I am main thread" << std::endl;
sleep(1);
pthread_cancel(tid);
}
return 0;
}
我们让主线程沉睡了5秒,5秒后新线程成功关闭
5.线程等待
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
线程等待没有之前的僵尸进程明显,但是每个线程创建后是需要我们对其进行回收的
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
测试代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread1(void *arg)
{
printf("thread 1 returning ... \n");
int *p = (int *)malloc(sizeof(int));
*p = 1;
return (void *)p;
}
void *thread2(void *arg)
{
printf("thread 2 exiting ...\n");
int *p = (int *)malloc(sizeof(int));
*p = 2;
pthread_exit((void *)p);
}
void *thread3(void *arg)
{
while (1)
{ //
printf("thread 3 is running ...\n");
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t tid;
void *ret;
// thread 1 return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %d, return code:%d\n",(int16_t)tid, *(int *)ret);
free(ret);
// thread 2 exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %d, return code:%d\n", (int16_t)tid, *(int *)ret);
free(ret);
// thread 3 cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if (ret == PTHREAD_CANCELED)
printf("thread return, thread id %d, return code:PTHREAD_CANCELED\n", (int16_t)tid);
else
printf("thread return, thread id %d, return code:NULL\n", (int16_t)tid);
return 0;
}
6. 分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg)
{
pthread_detach(pthread_self());
printf("%s\n", (char *)arg);
return NULL;
}
int main(void)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_run,(void *)"thread1 run...") != 0)
{
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); // 很重要,要让线程先分离,再等待
if (pthread_join(tid, NULL) == 0)
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}
7. 拓展实验–给线程传入结构体
线程的第一个参数是他的线程ID,第二个是它的属性一般设置为nullptr,第三个为执行的函数,第四个参数则是可以传入线程的参数,其类型是void*的类型,所以我们可以尝试传入任意类型
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <functional>
#include <string>
#include <ctime>
// typedef std::function<void()> func_t;
using func_t = std::function<void()>;
class ThreadData
{
public:
ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
: threadname(name), createtime(ctime), func(f)
{
}
public:
std::string threadname;
uint64_t createtime;
func_t func;
};
void Print()
{
std::cout << "我是线程执行的大任务的一部分" << std::endl;
}
// 新线程
void *ThreadRountine(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
std::cout << "new thread"
<< " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;
td->func();
sleep(1);
}
}
// 获取返回值
// 主线程
int main()
{
pthread_t tid;
ThreadData *td = new ThreadData("new thread", (uint64_t)time(nullptr), Print);
pthread_create(&tid, nullptr, ThreadRountine, td);
while (true)
{
std::cout << "main thread" << std::endl;
sleep(3);
}
}
8. 实现多线程
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <functional>
#include <string>
#include <ctime>
// typedef std::function<void()> func_t;
using func_t = std::function<void()>;
const int threadnum = 5;
//给线程传入的结构体
class ThreadData
{
public:
ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
: threadname(name), createtime(ctime), func(f)
{
}
public:
std::string threadname;
uint64_t createtime;
func_t func;
};
void Print()
{
std::cout << "我是线程执行的大任务的一部分" << std::endl;
}
// 新线程
void *ThreadRountine(void *args)
{
int a = 10;
ThreadData *td = static_cast<ThreadData *>(args); //类型转换为结构体指针
while (true)
{
std::cout << "new thread"
<< " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;
td->func();
sleep(1);
}
}
int main()
{
std::vector<pthread_t> pthreads;
for (size_t i = 0; i < threadnum; i++)
{
//snprintf函数实现输入线程名称
char threadname[64];
snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);
pthread_t tid;
//time时间戳函数
ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);
pthread_create(&tid, nullptr, ThreadRountine, td);
pthreads.push_back(tid);
sleep(1);
}
while (true)
{
std::cout << "main thread" << std::endl;
sleep(3);
}
}
可以看到这里右边有了六个线程,一个是我们的主线程。另外五个则是新创建的线程