嵌入式Linux下的多线程编程

持续更新,最后更新日期: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()
        直接结束进程(小心使用),该进程下的其它线程也会被结束
  • 示例
#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;
}

相关推荐

  1. 嵌入Linux线编程

    2024-02-22 11:26:02       41 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-22 11:26:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-22 11:26:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-22 11:26:02       82 阅读
  4. Python语言-面向对象

    2024-02-22 11:26:02       91 阅读

热门阅读

  1. Spring Boot

    2024-02-22 11:26:02       47 阅读
  2. Redis 数据结构详解:底层实现与高效使用场景

    2024-02-22 11:26:02       48 阅读
  3. C语言之删除中间的*

    2024-02-22 11:26:02       56 阅读
  4. 「Python系列」Python输入输出

    2024-02-22 11:26:02       59 阅读
  5. 喝点小酒-胡诌“编程语言学习”

    2024-02-22 11:26:02       46 阅读
  6. MySQL物理拷贝一张Innodb表的方法

    2024-02-22 11:26:02       42 阅读
  7. 记录 | 非root用户使用docker的方法

    2024-02-22 11:26:02       46 阅读
  8. 下拉框组件的封装(element ui )

    2024-02-22 11:26:02       49 阅读
  9. 通过傅里叶变换进行音频变声变调

    2024-02-22 11:26:02       50 阅读