持续更新,最后更新日期:2024/02/20
文章目录
一、理清进程和线程
1、进程和线程
1.1、进程的概念
- 进程是计算机系统中运行的一个程序的实例;
- 每个进程都有自己独立的内存空间,包括代码、数据和系统资源;
- 进程之间相互独立,需要通过进程间通信(IPC)机制来进行数据交换;
- 进程是操作系统进行资源分配和调度的基本单位;
1.2、线程的概念
- 线程是进程中的一个执行单元,一个进程可以包含多个线程;
- 线程共享进程的内存空间,包括代码、数据和系统资源;
- 线程之间通过共享内存直接进行数据交换,因此线程间的通信相对简单;
- 线程是操作系统进行调度的基本单位,相比进程更轻量级;
2、多进程和多线程
2.1、CPU处理器
从处理器说起会更好理解进程和线程的概念,这里把简单把处理器分为单核处理器和多核处理器。
- 单核处理器:只有一个物理核心,一次只能执行一个指令流;
- 多核处理器:包含多个物理核心,可以同时执行多个指令流,每个核心都是一个独立的处理单元;
2.2、基于单核处理器的多进程和多线程
- 多进程: 因为处理器是单核的缘故,本身应该并行同时处理的多进程变成了以时间片轮转的方式执行,这时候就叫伪并行或假并行;
- 多线程: 多线程的实现方式有两种主要模型,一种是通过时间片轮转来实现,在不同线程之间切换执行;另一种是通过事件驱动模型,其中一个线程等待事件的发生,一旦发生事件,就激活相应的线程来处理;总结来说,单核处理器下的多线程也不是并行处理的;
2.3、基于多核处理器的多进程和多线程
- 多进程: 因为处理器是多核的缘故,每个核心都可以独立执行指令,因此多个进程可以在不同的核心上同时运行,实现真正的并行性;
- 多线程: 多核处理器下的多线程和单核处理下的多线程略有不同,在多核处理器上,操作系统有更多的选择,可以更灵活地将线程调度到不同的核心上,以实现更好的并发性能,可以采用以下两种主要的线程调度模型:
- 时间片轮转;
- 绑定线程到核心: 操作系统也可以选择将线程绑定到特定的核心上。这样每个线程就会一直在分配的核心上执行,而不会频繁地切换到其他核心;
- 总结:多核处理器上的多线程处理并不一定只采用时间片轮转的方式,操作系统有能力更灵活地处理线程调度,以充分利用多核架构的优势,这种方式相对于单核系统更能发挥多核处理器的性能潜力;
二、Linux C API
1、获取线程id
- API
/*****************************
**@brief : 获取当前进程号
**@param : none
**@return: ID (PID)
*****************************/
pid_t getpid(void)
/*****************************
**@brief : 获取当前线程号
**@param : none
**@return: ID
*****************************/
pthread_t pthread_self(void)
- Tips
- /
- 示例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int main(int argc, char **argv)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("pid: %u \t tid: %lu\n", pid, tid);
return 0;
}
2、线程创建
- API
/*****************************
**@brief : 创建一个线程
**@param : thread 线程结构体,用于保存创建出来的线程
**@param : attr 线程属性
**@param : start_routine 线程处理函数
**@param : arg 线程处理函数参数
**@return: On success, pthread_create() returns 0; on error, it returns an error number.
*****************************/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- Tips
- 新线程可能在pthread_create()返回之前就已经开始运行,甚至在返回之前就已运行完毕,当然也可能主线程运行完了新线程还没结束;
- 示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void print_id(char *name)
{
pid_t pid = getpid();
pthread_t tid = pthread_self();
printf("%s : pid = %u \t tid = %lu\n", name, pid, tid);
}
void *thread_fun(void *arg)
{
print_id((char *)arg);
return (void *)0;
}
int main(int argc, char **argv)
{
pthread_t thread;
int err;
/* 新线程可能在pthread_create()返回之前就已经开始运行,
* 甚至在返回之前就已运行完毕,当然也可能主线程运行完了新线程还没结束
*/
err = pthread_create(&thread, NULL, thread_fun, "nThread");
if(err != 0)
printf("pthread_create err!\n");
print_id("main");
sleep(2);
return 0;
}
3、线程退出
- API
/*****************************
**@brief : 退出当前线程
**@param : retval 线程返回值
**@return: none
*****************************/
void pthread_exit(void *retval);
- Tips
- 线程退出的三种方式:
- return
只结束当前线程(常规方法,可以使用) - pthread_exit()
只结束当前线程(常规方法,可以使用) - exit()
直接结束进程(小心使用),该进程下的其它线程也会被结束
- return
- 线程退出的三种方式:
- 示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
int retval;
char *str = (char *)arg;
if(!strcmp("1", str))
{
// 1、只结束当前线程(常规方法,可以使用)
return (void *)0;
}
else if(!strcmp("2", str))
{
// 2、只结束当前线程(常规方法,可以使用)
pthread_exit(&retval);
}
else if(!strcmp("3", str))
{
// 3、直接结束进程(小心使用),该进程下的其它线程也会被结束
exit(0);
}
}
int main(int argc, char **argv)
{
pthread_t thread;
int err;
err = pthread_create(&thread, NULL, thread_fun, argv[1]);
if(err != 0)
printf("pthread_create err\n");
sleep(1);
printf("main thread\n");
return 0;
}
4、线程连接
- API
/*****************************
**@brief : The pthread_join() function waits for the thread specified by thread to terminate.
**@param : thread 指定要连接的线程
**@param : retval 线程返回值
**@return: On success, pthread_join() returns 0; on error, it returns an error number.
*****************************/
int pthread_join(pthread_t thread, void **retval);
- Tips
- 执行pthread_join()函数 的线程会阻塞,等待指定线程运行结束后继续运行;
- 要连接的线程不能是分离线程;
- 应用场景:
等待线程执行完毕;
- 示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_fun1(void *arg)
{
printf("i am thread_fun1\n");
return (void *)1;
}
void *thread_fun2(void *arg)
{
printf("i am thread_fun2\n");
pthread_detach( pthread_self() );
return (void *)2;
}
int main(int argc, char **argv)
{
pthread_t thread1, thread2;
int err;
void *rval1, *rval2;
err = pthread_create(&thread1, NULL, thread_fun1, NULL);
if(err != 0)
return -1;
err = pthread_create(&thread2, NULL, thread_fun2, NULL);
if(err != 0)
return -1;
printf("i am main\n");
//连接线程thread1,当前线程阻塞,等待thread1执行结束
err = pthread_join(thread1, &rval1);
if(err != 0)
printf("pthread_join thread1 err code : %d\n", err);
//连接线程thread2,当前线程阻塞,等待thread2执行结束
//但thread2线程里执行了pthread_detach()分离线程函数,故pthread_join()会返回错误码
err = pthread_join(thread2, &rval2);
if(err != 0)
printf("pthread_join thread2 err code : %d\n", err);
//rval保存线程返回值
printf("thread1 return: %d\n", (int *)rval1);
printf("thread2 return: %d\n", (int *)rval2);
printf("i am main\n");
return 0;
}
5、线程取消
- API
/*****************************
**@brief : 取消指定线程
**@param : thread 指定线程
**@return: On success, pthread_cancel() returns 0; on error, it returns a nonzero error number.
*****************************/
int pthread_cancel(pthread_t thread);
/*****************************
**@brief : 设置本线程对Cancel取消信号的反应,一种是响应,一种是忽略
**@param : state PTHREAD_CANCEL_ENABLE(响应取消信号)
PTHREAD_CANCEL_DISABLE(忽略取消信号)
**@param : oldstate 如果不为NULL则存入原来的Cancel状态以便恢复
**@return: On success, these functions return 0; on error, they return a nonzero error number.
*****************************/
int pthread_setcancelstate(int state, int *oldstate);
/*****************************
**@brief : 设置本线程取消动作的执行时机,仅在Cancel状态为Enable是有用
**@param : state PTHREAD_CANCEL_DEFERRED(延时取消,运行到下一取消点时执行取消动作)
PTHREAD_CANCEL_ASYNCHRONOUS(立即执行取消动作)
**@param : oldstate 如果不为NULL则存入原来的取消动作类型值
**@return: On success, these functions return 0; on error, they return a nonzero error number.
*****************************/
int pthread_setcanceltype(int type, int *oldtype);
- Tips
- 线程创建时默认使能响应取消信号,默认取消类型为延时取消;
- 被取消的线程如何知道自己要被取消?延时取消类型下,当程序执行到取消点时会检查自己是否要被取消,若被取消则取消;
- 什么是取消点?可以查看自己是否要被取消的地方称为取消点,取消操作是内部自己完成,取消点有很多,详细查看手册pthreads中的Cancellation points;
- 示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
int err;
//使能响应取消,当然线程被创建时就是默认使能的
err = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if(err != 0)
printf("pthread_setcancelstate err!\n");
//取消动作为延时取消,当然线程被创建时就是默认延时取消
err = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
if(err != 0)
printf("pthread_setcanceltype err!\n");
sleep(2);
//取消点,Cancel为Enable时,执行到这句时直接退出(不会打印)
printf("cancel point!\n");
//Cancel为Disable时,正常返回
return 0;
}
int main(int argc, char **argv)
{
pthread_t thread;
int err;
void *val;
err = pthread_create(&thread, NULL, thread_fun, NULL);
if(err != 0)
{
printf("pthread_create err!\n");
return -1;
}
//取消指定线程
err = pthread_cancel(thread);
if(err != 0)
printf("pthread_cancel err!\n");
err = pthread_join(thread, &val);
if(err != 0)
printf("pthread_join err!\n");
printf("i am main!\n");
printf("thread return value : %d\n", (int *)val);
return 0;
}
6、发生信号
- API
/*****************************
**@brief : 发送信号给指定线程
**@param : thread 指定线程
**@param : sig 信号,该参数为0时实际不发送信号,用于检测线程是否存在
**@return: On success, pthread_kill() returns 0; on error, it returns an error number, and no signal is sent.
*****************************/
int pthread_kill(pthread_t thread, int sig);
- Tips
- /
- 示例
- /
7、信号捕获
- API
/*****************************
**@brief : 绑定信号处理函数
**@param : signum 信号
**@param : act sigaction结构体
struct sigaction {
void (*sa_handler)(int); //信号集处理程序
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; //信号屏蔽字
int sa_flags;
void (*sa_restorer)(void);
};
**@return: sigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.
*****************************/
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
/*****************************
**@brief : 多线程信号屏蔽
**@param : how SIG_BLOCK(向当前的信号掩码中添加set,其中set表示要阻塞的信号组)
SIG_UNBLOCK(向当前的信号掩码中删除set,其中set表示要取消阻塞的信号组)
SIG_SETMASK(将当前的信号掩码替换为set,其中set表示新的信号掩码,
在多线程中,新线程的当前信号掩码会继承创造它的线程的信号掩码)
**@param : set sigset_t结构体
**@return: On success, pthread_sigmask() returns 0; on error, it returns an error number.
*****************************/
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
- Tips
- 在一个多线程的程序中,对于同一种信号,同一时刻只会有一个线程的信号处理函数被执行,当某个线程捕获了信号并开始执行信号处理函数时,其他线程对同一信号的处理函数将会被阻塞,直到当前线程的信号处理函数执行完毕;
- 要注意的是,如果在信号处理函数中调用了不可重入的函数(如printf),可能会导致不确定的行为,因为信号处理函数执行期间,对其他线程的阻塞可能导致一些全局状态的不一致性;
- 示例
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void sig_handler(int arg)
{
printf("sig_handler\n");
return;
}
void *thread_fun(void *arg)
{
struct sigaction act;
memset(&act, 0, sizeof(act));
sigaddset(&act.sa_mask, SIGQUIT);
act.sa_handler = sig_handler;
//绑定信号处理函数
sigaction(SIGQUIT, &act, NULL);
//屏蔽SIGQUIT信号
//当收到SIGQUIT信号,不会执行sig_handler()
//注释此句,则会执行sig_handler()
pthread_sigmask(SIG_BLOCK, &act.sa_mask, NULL);
sleep(2);
return (void *)0;
}
int main(int argc, char **argv)
{
pthread_t thread;
int err;
err = pthread_create(&thread, NULL, thread_fun, NULL);
if(err != 0)
{
printf("pthread_create err!\n");
return -1;
}
sleep(1);
pthread_kill(thread, SIGQUIT);
if(err != 0)
{
printf("pthread_kill err!\n");
return -1;
}
pthread_join(thread, NULL);
printf("i am main\n");
return 0;
}