目录
[一. 线程](#一. 线程)
[1.1 线程概念](#1.1 线程概念)
[1.2 线程与进程的区别](#1.2 线程与进程的区别)
[二. 线程相关函数接口](#二. 线程相关函数接口)
[2.1 pthread_create](#2.1 pthread_create)
[2.2 pthread_exit](#2.2 pthread_exit)
[2.3 pthread_join](#2.3 pthread_join)
[2.3.2 注意](#2.3.2 注意)
[2.3.3 示例代码](#2.3.3 示例代码)
[2.4 pthread_detach](#2.4 pthread_detach)
[三. 线程的互斥](#三. 线程的互斥)
[3.1 线程的互斥机制](#3.1 线程的互斥机制)
[3.2 保护临界资源-互斥锁](#3.2 保护临界资源-互斥锁)
[3.2.1 pthread_mutex_init](#3.2.1 pthread_mutex_init)
[3.2.2 pthread_mutex_lock/trylock](#3.2.2 pthread_mutex_lock/trylock)
[3.2.4 pthread_mutex_unlock(*mutex)](#3.2.4 pthread_mutex_unlock(*mutex))
[3.2.5 pthread_mutex_destroy(*mutex)](#3.2.5 pthread_mutex_destroy(*mutex))
[四. 多线程模块的封装](#四. 多线程模块的封装)
一. 线程
1.1 线程概念
**线程:**线程是轻量级进程(LWP)

1.2 线程与进程的区别
|-----------------------------|--------------------------|
| 进程 | 线程 |
| 正在执行的程序 | 轻量级进程 |
| 操作系统资源分配的最小单位 | 操作系统任务调度的最小单位 |
| 资源空间消耗大,0~4G虚拟内存地址空间 | 资源空间消耗小,栈区独立,其他空间共享 |
| 进程的效率低 | 线程效率高 |
| 进程安全性高 | 线程安全性低 |
| 进程间通信复杂,需要用到IPC机制 | 线程间通信方便,由于共享数据区,使用全局变量即可 |
| 在相同资源的平台下,多进程的并发量要少于多线程的并发量 | |
二. 线程相关函数接口
2.1 pthread_create

**作用:**会在调用进程内启动一个新的线程
参数:*thread:保存新线程的id
*attr:线程属性
*start_routine:回调函数,线程的执行函数
*arg:给回调函数的参数
**返回值:**成功返回0 失败返回一个错误号
2.2 pthread_exit

作用: pthread_exit() 函数会终止调用该函数的线程,并通过retval参数返回一个值;而这个值则
可供同一进程 中调用 pthread_join() 的其他线程使用。
当一个线程终止时,进程共享资源(例如互斥锁、条件变量、信号量和文件描述符)不会被
释放
当进程中的最后一个线程终止后,该进程便会像调用**exit()**并设置退出状态为零那样结束;
因此,进程共享资源会被释放。
参数:*retval:退出值
**返回值:**无
2.3 pthread_join

作用:阻塞等待线程退出
pthread_join() 函数会等待由 thread 参数指定的线程完成其执行过程。如果该线程已经结
束执行,那么 pthread_join() 将立即返回。这个由 thread指定的线程必须是非分离状态
如果 retval 不为 NULL,则 pthread_join() 会将目标线程的退出状态复制到由 retval 所指向
的位置。如果目标线程被取消(pthread_cancel ),则会在由 retval 所指向的位置放置 **PTHREAD_CANCELED (-1)**标志。
**参数:**thread:指定线程 **retval:保存线程退出状态
**返回值:**成功返回0 失败返回一个错误号
2.3.1对比记忆
线程与进程在接收返回值时的对比:
|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 进程 | 线程 |
| 进程在退出时可使用:return 0;return -1;exit(1); exit(0);_exit(0);_Exit(0)等 可以看到进程的退出状态都是整型 | 线程的退出可使用:return NULL;pthread_exit(NULL)等 可以看到线程的退出状态都是指针类型,另外,在pthread_create 创建线程时,那个回调函数的返回类型就是void* 类型,这就规定了线程的执行逻辑的返回值类型是void* ,指针类型 |
| 在获取进程的退出状态时,使用函数:wait 或者waitpid ,其中有一个参数int *wstatus 来保存进程的退出状态,然后使用各种宏来获取进程的退出值等 由于进程的退出状态是int,故用int*来接收 | 获取线程的退出状态时,使用pthread_join,第二个参数 void **retval就是用来保存线程的退出状态的 由于线程的退出状态是void*,故使用void**来接收 |
2.3.2 注意
**注意:**使用pthread_exit不可返回栈区空间的地址,因为线程结束,栈区空间释放,会出现段错误。
以下指针可以通过函数返回:全局变量的地址
static修饰的局部变量的地址
堆区申请的未被释放的空间的地址
字符串常量的地址
通过函数传参传过来的地址
2.3.3 示例代码
cpp
int num_g = 66;
void *th_task1(void *arg)
{
int cnt = 5;
while(cnt--){
printf("---thread 1 :%ld,pid: %d\n",pthread_self(),getpid());
sleep(1);
}
//return &num_g;
return (void*)666;
}
void *th_task2(void *arg)
{
while(1){
printf("---thread 2 :%ld, pid : %d\n",pthread_self(), getpid());
sleep(1);
}
return NULL;
}
int main()
{
int ret;
void *retval;
pthread_t tid1, tid2;
ret = pthread_create(&tid1, NULL,th_task1,NULL);
if(ret != 0){
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(1);
}
ret = pthread_create(&tid2, NULL,th_task2,NULL);
if(ret != 0){
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(1);
}
printf("---thread main :%ld, pid : %d\n",pthread_self(), getpid());
printf("main:%ld,%ld\n",tid1,tid2);
pthread_join(tid1, &retval);
//printf("---pthread join finish:%ld,retval is %d\n",tid1,*(int*)retval);
printf("---pthread join finish:%ld,retval is %ld\n",tid1,(long)retval);
/这种情况下最好将接收的状态值强转为long型,因为64位系统下long与指针都是8位
pthread_join(tid2, NULL);
return 0;
}
结果:

2.4 pthread_detach

**作用:**pthread_detach() 函数将由 thread 标识的线程标记为已分离状态。当一个已分离的线程终止时,其资源会自动返回给系统,无需其他线程与已终止的线程进行连接。试图分离一个已经分离的线程会导致未定义的行为。
**参数:**thread指定的线程
**返回值:**成功返回0 失败返回一个错误号
**分离线程:**不需要回收的线程,当线程结束时可以被操作系统自动回收其资源,类似孤儿进程。
**非分离线程:**可以被其他回收或者结束的线程,默认属性为非分离线程
示例代码:
cpp
void *th_task1(void *arg)
{
return NULL;
}
int main()
{
pthread_t tid;
int num = 0;
while(1){
num++;
printf("num is %d\n",num);
pthread_create(&tid, NULL,th_task1,NULL);
pthread_detach(tid);
}
return 0;
}
三. 线程的互斥
3.1 线程的互斥机制
多线程在访问临界资源时存在资源竞争!
临界资源: 多个线程可以同时访问的资源,如全局变量,共享内存等
**线程互斥机制:**让多个线程在访问临界资源时,具有排他访问的特性
3.2 保护临界资源-互斥锁
互斥锁:
使用流程:创建锁->初始化锁->加锁->访问临界资源->解锁->销毁锁
3.2.1 pthread_mutex_init

功能: 初始化互斥锁
参数: 互斥锁对象
锁的属性
**返回值:**成功返回0 失败返回错误码
3.2.2 pthread_mutex_lock/trylock

功能: pthread_mutex_lock:以阻塞方式等待锁
pthread_mutex_trylock:以非阻塞方式等待锁
**返回值:**成功返回0 失败返回错误号
3.2.4 pthread_mutex_unlock(*mutex)

功能:解锁
**返回值:**成功返回0 失败返回错误号
3.2.5 pthread_mutex_destroy(*mutex)

**功能:**销毁锁
示例代码:模拟atm取钱程序:
cpp
pthread_mutex_t mutex;
int atm = 3;
void * pfun(void *arg)
{
while(1){
//访问atm公共资源,先加锁
pthread_mutex_lock(&mutex);
if(atm > 0){
printf("atm = %d\n",atm);
atm--;
printf("--[%ld]:draw money---\n",pthread_self());
pthread_mutex_unlock(&mutex);//解锁后,取钱
sleep(rand() % 3 + 1);
pthread_mutex_lock(&mutex);//加锁,再访问atm
atm++;
pthread_mutex_unlock(&mutex);//解锁
return NULL;
}
else{
pthread_mutex_unlock(&mutex);
}
}
}
int main()
{
pthread_t tid[10];
int ret;
int i = 0;
srand(time(NULL));
pthread_mutex_init(&mutex, NULL);
//10个线程去atm取钱
for(i = 0; i < 10; i++){
ret = pthread_create(&tid[i], NULL, pfun, NULL);
if(ret != 0){
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(1);
}
}
for(i = 0; i < 10; i++){
ret = pthread_join(tid[i], NULL);
if(ret != 0){
fprintf(stderr,"pthread_join error:%s\n",strerror(ret));
exit(1);
}
}
pthread_mutex_destroy(&mutex);
printf("main:draw money finish!\n");
return 0;
}
四. 多线程模块的封装
tasks.c
cpp
/创建多个线程
int creat_thread_tasks(Task_t *tasks, int len)
{
for(int i = 0; i < len; i++){
int ret = pthread_create(&(tasks[i].tid), NULL, tasks[i].pfun, NULL);
if(ret != 0 ){
fprintf(stderr, "pthread_create error:%s\n",strerror(ret));
return -1;
}
}
return 0;
}
/销毁多个线程
void destory_thread_tasks(Task_t *tasks, int len)
{
for(int i = 0; i < len; i++){
int ret = pthread_join(tasks[i].tid, NULL);
}
}
tasks.h
cpp
#ifndef __TASKS_H__
#define __TASKS_H__
#include<pthread.h>
typedef struct task{
pthread_t tid;
void *(*pfun)(void*);
}Task_t;
extern int creat_thread_tasks(Task_t *tasks, int len);
extern void destory_thread_tasks(Task_t *tasks, int len);
#endif
main.c
cpp
#include<stdio.h>
#include "tasks.h"
#include<unistd.h>
#include<stdlib.h>
//#include<pthread.h>
void *main_ctl_task(void *arg)
{
while(1){
printf("[%ld]:main_ctl_task is running\n",pthread_self());
sleep(1);
}
}
void *get_usr_cmd_task(void *arg)
{
while(1){
printf("[%ld]:get_usr_cmd_task is running\n",pthread_self());
sleep(1);
}
}
void *exec_usr_cmd_task(void *arg)
{
while(1){
printf("[%ld]:exec_usr_cmd_task is running\n",pthread_self());
sleep(1);
}
}
void * get_pic_task(void *arg)
{
while(1){
printf("[%ld]:get_pic_task is running\n",pthread_self());
sleep(1);
}
}
void *send_pic_task(void *arg)
{
while(1){
printf("[%ld]:send_pic_task is running\n",pthread_self());
sleep(1);
}
}
/Linux支持的部分初始化方式
Task_t tasks[] =
{
{
.pfun = main_ctl_task,
},
{
.pfun = get_usr_cmd_task,
},
{
.pfun = exec_usr_cmd_task,
},
{
.pfun = get_pic_task,
},
{
.pfun = send_pic_task,
},
};
int main()
{
int ret = creat_thread_tasks(tasks,sizeof(tasks)/sizeof(tasks[0]));
if(ret == -1){
fprintf(stderr, "creat_thread_tasks error\n");
exit(1);
}
destory_thread_tasks(tasks,sizeof(tasks)/sizeof(tasks[0]));
return 0;
}