线程
与进程类似,线程(thread)是允许程序并发执行多任务的一种机制。一个进程可以包含多个线程,每个线程均会独立执行相同的程序,且共享同一份全局内存区域。其中包括初始化数据段、未初始化数据段,以及堆内存段
同一进程中的多个线程可以并发执行。在多处理器环境下,多个线程可以同时并行。如果一个线程因等待 I/O 操作而遭阻塞,那么其他线程依然可以继续运行
进程和线程都可以实现多块代码同时执行
线程相对进程的优势:
- 创建进程的代价大:创建进程会复制父进程的内容,需要额外分配资源,可能会造成资源的浪费;线程比进程的创建要快很多
- 进程间难以共享:进程间的信息难以共享,需要进行进程间的通信;线程间能方便、快速的共享信息,不过要避免多个线程同时修改同一数据的情况
创建线程
启动程序时,产生的进程只有单条线程,称之为初始(initial)或主(main)线程。函数 pthread_create() 负责创建一条新线程。新线程通过调用带有参数 arg 的函数 start_routine(即 start_routine(arg))而开始执行。调用 pthread_create() 的线程会继续执行该调用之后的语句。
进程内部的每个线程都有一个唯一标识,称为线程 ID。线程获取自己的线程 ID 使用pthread_self() 函数。
使用命令 ps -eLf 查看线程
ubuntu20 编译有线程的代码可能失败,使用 gcc 编译,加参数 -lpthread
pthread_self() 函数
函数描述:
获取线程 ID。类似于进程中的 getpid() 函数。线程 ID 是进程内部的识别标志(两个进程间,允许线程 ID 相同)
函数原型:
pthread_t pthread_self(void);
函数返回值:
成功:返回线程 ID,pthread_t 为无符号整型(%lu)
失败:无
pthread_create() 函数
函数描述:
创建一个新线程。 类似进程中的 fork() 函数。
头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函数参数:
thread:传出参数,保存系统为我们分配好的线程 ID
attr:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束
arg:线程主函数的参数
函数返回值:
成功:返回0
失败:返回错误号
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
void* thread1(void* arg)
{
printf("thread1 tid = %lu\n", pthread_self());
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
printf("main tid = %lu\n", pthread_self());
while(1);
return 0;
}
线程间可以共享全局变量,局部变量只能通过传参共享 (多个局部变量,如果同类型传数组,不同类型传结构体)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
printf("thread1 tid = %lu\n", pthread_self());
a = 1;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
printf("main tid = %lu\n", pthread_self());
sleep(1);
printf("a = %d\n", a);
while(1);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
void* thread1(void* arg)
{
printf("b = %d\n", *(int*)arg);
}
int main(int argc, char* argv[])
{
pthread_t tid;
int b = 1;
pthread_create(&tid, NULL, thread1, &b);
printf("main tid = %lu\n", pthread_self());
while(1);
return 0;
}
终止线程
终止线程的方式:
- 线程 start_routine 函数执行 return。
- 线程调用 pthread_exit() 终止线程。
- 任意线程调用了 exit(),或者主线程执行了 return 语句(在 main()函数中),都会导致进程中的所有线程立即终止
- 调用 pthread_cancel(thread) 取消指定线程
pthread_exit() 函数将终止调用线程,且其返回值可由另一线程通过调用 pthread_join() 来获取
调用 pthread_exit() 相当于在线程的 start_routine 函数中执行 return,不同之处在于,可在线程 start_routine 函数所调用的任意函数中调用 pthread_exit() 都能够直接终止线程 。
pthread_exit() 函数
函数描述:
终止线程
函数原型:
void pthread_exit(void *retval);
函数参数:
retval:表示线程退出状态,通常传NULL
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
while(a < 10)
{
printf("a = %d\n", a++);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
sleep(3);
return 0; //进程结束,进程中包含线程,故线程也结束
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
while(a < 10)
{
printf("a = %d\n", a++);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
sleep(3);
exit(0); //结束进程的函数,同上
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
while(a < 10)
{
printf("a = %d\n", a++);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
sleep(3);
pthread_exit(NULL); //结束线程的函数,只将主线程结束,子线程继续运行
return 0;
}
pthread_cancel() 函数
函数描述:
向指定线程发送一个取消请求。发出取消请求后,函数 pthread_cancel() 当即返回,不会等待目标线程的退出。被请求取消的线程不会立即取消,需要等待到达某个取消点。取消点通常是一些系统调用,也可以使用pthread_testcancel()函数手动创建一个取消点,被取消的线程返回值是PTHREAD_CANCELED(-1)。
函数原型:
int pthread_cancel(pthread_t thread);
函数参数:
thread:要取消的线程ID
函数返回值:
成功:返回0
失败:返回错误号
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
while(1)
{
if(a == 10)
{
a = 0;
}
a++;
pthread_testcancel(); //设置取消点
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
int ret = pthread_cancel(tid);
if(ret != 0)
{
printf("pthread_cancel error, ret = %d\n", ret);
}
else
{
printf("pthread_cancel success, rret = %d\n", ret);
}
while(1)
{
printf("a = %d\n", a);
sleep(1);
}
return 0;
}
线程的连接与分离
pthread_join() 函数
函数描述:
等待指定线程终止并回收,这种操作叫做连接(joining)。未连接的线程会产生僵尸线程,类似僵尸进程的概念。
函数原型:
int pthread_join(pthread_t thread, void **retval);
函数参数:
thread:要等待的线程ID
retval:传出参数,存储线程返回值
函数返回值:
成功:返回0
失败:返回错误号
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
sleep(5);
return (void*)"abc";
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
void* ret;
pthread_join(tid, &ret);
printf("retval = %s\n", (char*)ret);
return 0;
}
pthread_detach() 函数
函数描述:
默认线程是可连接的(joinable),当线程退出时,其他线程可以通过调用 pthread_join() 获取其返回状态。有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动清理并移除。这时可以调用 pthread_detach() 函数,将线程标记为分离(detached)状态。
函数原型:
int pthread_detach(pthread_t thread);
函数参数:
thread:要分离的线程ID
retval:存储线程返回值
函数返回值:
成功:返回0
失败:返回错误号
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
sleep(5);
return (void*)"abc";
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
pthread_detach(tid);
sleep(10);
void* ret;
pthread_join(tid, &ret);
printf("retval = %s\n", (char*)ret);
return 0;
}
练习
定义一个全局变量a,创建10个线程,每个线程对a进行自增(a++)1万次,所有线程自增结束主线程打印a的值。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>
int a = 0;
void* thread1(void* arg)
{
for(int i = 0; i < 10000; i++)
{
a++;
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid[10];
for(int i = 0; i < 10; i++)
{
pthread_create(&tid[i], NULL, thread1, NULL);
}
for(int i = 0; i < 10; i++)
{
pthread_join(tid[i], NULL);
}
printf("a = %d\n", a);
return 0;
}