文章目录
线程概述
线程的概念
每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。为了减少系统开销,从进程中演化出了线程。
线程存在于进程中,共享进程的资源。线程是进程中的独立控制流,由环境(包括寄存器组和程序计数器)和一系列的执行指 令组成。每个进程有一个地址空间和一个控制线程。
线程和进程的比较
调度
线程是CPU调度和分派的基本单位。
拥有资源
进程是系统中程序执行和资源分配的基本单位。线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备 等)。
系统开销
同一个进程中的多个线程可共享同一地址空间,因此它们之间的同步和通信的实现也变 得比较容易。
在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU 环境的设置;而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面操作,从而能更有效地使用系统资源和提高系统的吞吐量。
并发性
不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。
总结
一般把线程称之为轻量级的进程 一个进程可以创建多个线程,多个线程共享一个进程的资源。
每一个进程创建的时候系统会给其4G虚拟内存,3G用户空间是私有的,所以进程切换时,用户空间也会切换,所以会增加系统开销,而一个进程中的多个线程共享一个进程的资源,所以线程切换时不用切换这些资源,效率会更高 线程的调度机制跟进程是一样的,多个线程来回切换运行。
多线程的用处
使用多线程的目的主要有以下几点:
多任务程序的设计
一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开 销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
并发程序设计
一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
网络程序设计
为提高网络的利用效率,我们可能使用多线程,对每个连接用一个线程去处理。
数据共享
同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。
线程的基本操作
线程的创建
注意事项
与fork不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指 定的函数开始运行,该函数运行完后,该线程也就退出了。
线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。
线程函数的程序在pthread库中,故链接时要加上参数-lpthread。
例子
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//由于线程库原本不是系统本身的,所以在链接时需要手动链接库文件 gcc *.c -lpthread
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
//通过pthread_create函数创建子线程
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//由于进程结束后,进程中所有的线程都会强制退出,所以现阶段不要让进程退出
while(1);
return 0;
}
线程调度机制的验证
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//一个进程中的多个线程执行顺序是不确定的,没有先后顺序可言
//多线程执行时跟进程一样,是来回切换运行的,跟进程的调度机制一样
void *pthread_fun1(void *arg)
{
printf("子线程1正在运行\n");
sleep(1);
printf("**********************\n");
}
void *pthread_fun2(void *arg)
{
printf("子线程2正在运行\n");
sleep(1);
printf("-----------------------\n");
}
int main(int argc, char const *argv[])
{
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
while(1);
return 0;
}
线程处理函数的传参
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
int num = 100;
//线程处理函数可以认为就是一个普通的全局函数,只不过与普通函数最大的区别
//在于,线程处理函数是并行执行,来回交替执行,但是普通函数一定是按照顺序一个一个执行
void *pthread_fun1(void *arg)
{
printf("子线程1:num = %d\n", num);
num++;
int n = *(int *)arg;
printf("1 n = %d\n", n);
*(int *)arg = 111;
}
void *pthread_fun2(void *arg)
{
sleep(1);
printf("子线程2:num = %d\n", num);
int n = *(int *)arg;
printf("2 n = %d\n", n);
}
int main(int argc, char const *argv[])
{
pthread_t thread1, thread2;
int a = 666;
if(pthread_create(&thread1, NULL, pthread_fun1, (void *)&a) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, (void *)&a) != 0)
{
perror("fail to pthread_create");
}
while(1);
return 0;
}
线程等待
通过pthread_join函数等待子线程退出
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
sleep(3);
printf("子线程要退出了\n");
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//通过调用pthread_join函数阻塞等待子线程退出
if(pthread_join(thread, NULL) != 0)
{
perror("fail to pthread_join");
exit(1);
}
printf("进程要退出了\n");
return 0;
}
通过pthread_join获取子线程的退出状态值
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
static int num = 666;
printf("子线程正在运行\n");
sleep(3);
printf("子线程要退出了\n");
//子线程如果要返回退出状态,可以通过返回值或者通过pthread_exit函数
return (void *)#
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
int *num;
if(pthread_join(thread, (void **)&num) != 0)
{
perror("fail to pthread_join");
exit(1);
}
printf("ret_val = %d\n", *num);
printf("进程要退出了\n");
return 0;
}
线程分离
线程的结合态和分离态
linux线程执行和windows不同,pthread有两种状态:
可结合的(joinable)或者是分离的(detached),线程默认创建为可结合态。
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是detached状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放,使用pthread_detach函数将线程设置为分离态。
创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了 线程分离函数:pthread_detach
线程分离函数:pthread_detach
通过pthread_detach函数将子线程设置为分离态,既不用阻塞,也可以自动回收子线程退出的资源。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
sleep(3);
printf("子线程要退出了\n");
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//通过pthread_detach函数将子线程设置为分离态,既不用阻塞,也可以自动回收子线程退出的资源
if(pthread_detach(thread) != 0)
{
perror("fail to pthread_detach");
exit(1);
}
//如果原本子线程是结合态,需要通过pthrad_join函数回收子线程退出的资源,
//但是这个函数是一个阻塞函数,如果子线程不退出,就会导致当前进程(主控线程)
//无法继续执行,大大的限制了代码的运行效率
//如果子线程已经设置为分离态,就不需要再使用pthread_join了
#if 0
if(pthread_join(thread, NULL) != 0)
{
perror("fail to pthread_join");
exit(1);
}
#endif
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
线程退出
线程退出函数
一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并 不会释放。如果要释放资源,结合态需要通过pthread_join函数,分离态则自动释放。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
static char buf[] = "This thread has quited";
int i;
for(int i = 0; i < 10; i++)
{
if(i == 5)
{
//通过pthread_exit函数退出当前线程
//pthread_exit(NULL);
pthread_exit(buf);
}
printf("*******************\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//pthread_join(thread, NULL);
char *str;
pthread_join(thread, (void **)&str);
printf("str = %s\n", str);
printf("进程要退出了\n");
return 0;
}
线程的取消pthread_cancel()
pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。 此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。
然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决定线程能否被取消以及何时被取消。
线程的取消状态:即线程能不能被取消。
线程取消点:即线程被取消的地方。
线程的取消类型:在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
//通过调用pthread_cancel函数取消另一个线程
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
线程的取消状态pthread_setcancelstate()
在Linux系统下,线程默认可以被取消。编程时可以通过pthread_setcancelstate函数 设置线程是否可以被取消。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
//通过pthread_setcancelstate设置取消的状态
//设置为可以取消
//pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
//设置为不可取消
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
线程的取消点pthread_testcancel()
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。编 程时可以通过pthread_testcancel函数设置线程的取消点。
void pthread_testcancel(void);
当别的线程取消调用此函数的线程时候,被取消的线程执行到此函数时结束。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
while(1)
{
printf("子线程正在运行\n");
sleep(1);
pthread_testcancel();
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
线程的取消类型pthread_setcanceltype()
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。编程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。
不立即取消
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
//设置线程取消的类型
//设置为立即取消
//pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
//设置为不立即取消
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
线程退出清理
和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程 清理处理程序(thread cleanup handler)。
注意:
线程可以建立多个清理处理程序。
处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。
当线程执行以下动作时会调用清理函数:
1、调用pthread_exit退出线程。
2、响应其它线程的取消请求。
3、用非零execute调用pthread_cleanup_pop。
pthread_cleanup_push()
pthread_cleanup_pop()
验证线程调用pthread_exit函数时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void mycleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
/*建立线程清理程序*/
printf("this is new thread\n");
char *ptr = NULL;
ptr = (char*)malloc(100);
pthread_cleanup_push(mycleanup, (void*)(ptr));
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
sleep(3);
printf("before exit\n");
pthread_exit(NULL);
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
验证线程被取消时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup, (void*)(ptr));
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
sleep(10);
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
sleep(5);
printf("before cancel\n");
/*子线程响应pthread_cancel后,会执行线程处理函数*/
pthread_cancel(tid);
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
验证调用pthread_cleanup_pop函数时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup_func1(void *arg)
{
printf("in cleanup func1\n");
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void cleanup_func2(void *arg)
{
printf("in cleanup func2\n");
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup_func1, (void*)(ptr));
pthread_cleanup_push(cleanup_func2, NULL);
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
/*注意push与pop必须配对使用,即使pop执行不到*/
sleep(3);
printf("before pop\n");
pthread_cleanup_pop(1);
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}