Linux下线程理解

1.线程的概念

线程,在操作系统当中这是一个相当重要的概念,线程的存在是为了解决在一个进程当中执行多个执行分支的任务,举一个很简单的例子:当你在用某个下载软件的时候,相当于你在运行该进程,而你在通过该软件进行下载时,很明显你在同一时间并不是只能进行一个下载任务,而且你在下载的时候,你可以使用键盘,鼠标来执行其他任务,而这些任务都是在同一个进程中在跑,这就是多线程的强大之处。

其实相对于多线程而言,在我们的操作系统当中还存在着多进程的概念,而两者究竟有何区别呢?

要回答这个问题,就不得不提我们的操作系统中,线程与进程究竟有什么区别了。

对于Linux来说,每一个进程都有他们独立的地址空间,独立的PCB,他们之间的数据是相互独立的,而且对于每个进程而言,都是相当的有野心,认为自己有权利支配整个内存空间(从地址空间的地址范围是从0x00000000到0xf f f f f f f f 就能看出),而由于每个进程的地址空间是独立的,所以在创建一个进程的时候,必然要有对应的地址空间,以及其他的相关内容,光是分配它独立的地址空间这一点,对于操作系统而言就是相当大的开销,若是每一个执行分支就是一个进程的话,那么这笔开销是相当大的,况且对于多个执行分支之间必然会有信息的交流,进程间通信则是必不可少了,而我们知道进程间通信是有些复杂的,对于多个执行分支之间频繁的通信,这是相当不现实的。

而在引入多线程之后,这就完全不一样了。首先,我们的同一个进程的多个线程之间是共享同一个地址空间的,这样就为操作系统节省了一大笔开销,而且由于共享同一个地址空间,所以线程之间的数据有相当大的一部分是共享的,这也省去了进程间频繁通信所带来的麻烦。但是也由于共享地址空间这样的进制,导致某些数据被多个线程同时进行修改,从而造成程序的运行的错误(比如static变量所带来的巨大麻烦),而这也是多线程编程所绕不开的问题。

而且虽说线程之间有很多的资源是共享的,如文件描述符表(不同的线程之间共享文件偏移量),当前用户id与组id,以及每种信号的处理方式,工作目录等,但是对于线程而言,也是有一部分的资源是必须私有的:

①线程id(用于标识一个线程);

②上下文信息(在线程中断之后,用于恢复的寄存器,栈指针,程序计数器的信息);

③栈空间(每个线程必须有其独立的栈帧空间);

④errno变量,信号屏蔽字,调度优先级等;


但是话说回来,对于我们的Linux,其实并不认识所谓的线程,我们在标识一个线程的时候,同样也是用PCB(task_struct)这一模块来进行描述的,并且让它供调度器调度,也就是说共享地址空间这一机制确实存在,但是在Linux调度时看来,线程与进程并无差别,由此Linux下的线程也叫我们的“轻量级进程”。

所以,我们可以明白一点,对于一个进程来说,若是其中只有一个执行分支,那么也就可以认为这个进程中只有一个线程(主线程),供调度的只有一个线程,而对于多线程的进程而言,每一个线程都有自己的PCB,供调度的也就是这些PCB所对应的线程,由此我们可以明白,在Linux下是将一个个的进程给细分了,分成一个个对应的线程,所以Linux下的供CPU调度单位是我们的线程,而我们的进程是用来承担资源的分配的基本单位。


2.线程控制

以下所有函数均为POSIX标准下的线程库函数,用gcc编译时要加-lpthread选项,要引入pthread.h头文件。

①创建线程

函数原型:

int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);

函数功能:调用该函数之后,分成两个执行流,向下执行为主线程。

函数参数:第一个参数thread是指向线程标识符的指针,第二个参数用来设置线程状态,第三个参数用来表示线程运行函数的起始地址(返回值为void*,参数为void*的函数指针),第四个参数表示运行函数的参数。

返回值:若创建线程成功返回0,否则返回错误码。


②线程等待

函数原型:

int pthread_join(pthread_t thread,void** retval);//接收的退出码类型视情况而定
函数功能:调用该函数后,主线程发生阻塞式等待,等待成功后将线程资源进行回收。

函数参数:第一个参数thread表示要等待的线程标识符,第二个参数用于接收线程的退出码(只能用于监视新线程正常退出的退出码,若是异常退出,那么整个进程就会异常退出了,其他线程(包括主线程)也自然就异常退出了)。


这里得对第二个参数的情况进行说明:(也就是线程退出的三种情况)

a.线程函数走到末尾返回,则retval指向的空间接收return的值;

b.线程被取消pthread_cancel,则retval指向的空间被置为PTHREAD_CANCELED[  (void*)-1  ];

c.线程函数走到pthread_exit退出,则retval指向的空间接收pthread_exit的参数;


返回值:若等待成功,则返回0,否则返回错误码。


③线程退出

函数原型:

void pthread_exit(void* retval);//退出码类型可自行指定
函数功能:用于终止当前线程,这里需要注意一点线程的退出不能用exit(),exit()函数是用于进程退出的。

函数参数:唯一参数retval用于接收线程退出码(这里的retval指向的变量必须是全局变量或者是动态申请的堆上空间,决不能是栈空间的变量)。


④线程取消

函数原型:

int pthread_cancel (pthread_t thread);
函数功能:对指定的线程进行终止

函数参数:唯一参数thread表示要终止的线程标识符

返回值:若成功返回0,否则返回非0错误码


3.线程分离

我们的线程的状态在任一时刻,大致分为两种:可结合的(joinable)和可分离的(detached),一个线程默认是可结合的。

首先,当一个线程处于可结合的状态时,在线程终止后,该线程所占用的资源是不会被回收的,若是没有其他线程对其调用pthread_join进行等待的话,那么就会造成这部分的资源无法被回收,则该线程处于类似于僵尸进程的状态,这将会占用我们的操作系统资源的,这种情况我们是要尽量规避的,所以对于可结合的线程,我们会在其他线程(主线程)中调用pthread_join进行等待,回收其资源;

但是,在其他的线程中调用pthread_join,不可避免的会造成阻塞式等待,这样的话在某些情况下是不想看到的,但是线程的资源又必须得回收,由此我们就可以将该线程设置为可分离的,让这个线程在终止的时候就将它所占用的资源进行释放。而为了实现这一目标,我们就得调用下面的接口:

函数原型:

int pthread_detach(pthread_t thread);
函数功能:将线程设置为可分离的(detached),线程终止时对所占用资源进行自动释放,非阻塞的

参数:唯一参数thread表示所要设置成可分离的状态的线程的线程标识符,在子线程中可以用pthread_self()进行获取,在父线程中可传入对应线程标识符

返回值:若成功,则返回0,否则返回对应错误码

这里需要注意的一点是:在调用pthread_detach成功后,再在其他线程中调用pthread_join进行等待就会失败。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux系统下,可以使用线程实现串口通信例程。 首先,需要引入一些头文件,如`<stdio.h>, <stdlib.h>, <unistd.h>, <fcntl.h>, <termios.h>, <pthread.h>`,以便使用相关函数和数据结构。 接下来,打开串口设备文件,使用`open()`函数,并通过`<fcntl.h>`中的`O_RDWR`参数设置为可读写模式。例如,打开`/dev/ttyS0`串口设备: ```c int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd == -1) { perror("打开串口失败"); exit(EXIT_FAILURE); } ``` 然后,配置串口属性,包括波特率、数据位、停止位等。首先需要获取当前串口属性,使用`tcgetattr()`函数,并通过`<termios.h>`中的数据结构`struct termios`进行配置。例如,设置波特率为115200: ```c struct termios attr; if (tcgetattr(fd, &attr) == -1) { perror("获取串口属性失败"); close(fd); exit(EXIT_FAILURE); } cfsetispeed(&attr, B115200); cfsetospeed(&attr, B115200); if (tcsetattr(fd, TCSANOW, &attr) == -1) { perror("设置串口属性失败"); close(fd); exit(EXIT_FAILURE); } ``` 接下来,创建一个线程,用于接收串口数据。使用`pthread_create()`函数,并编写线程函数。例如,以下为接收串口数据的线程函数: ```c void *receiveThread(void *arg) { char buffer[256]; int len; while (1) { len = read(fd, buffer, sizeof(buffer)); if (len > 0) { // 处理接收到的数据 // ... } } return NULL; } pthread_t tid; pthread_create(&tid, NULL, receiveThread, NULL); ``` 最后,主线程(或其他线程)可以通过`write()`函数向串口发送数据。例如,向串口发送一个字符串: ```c char *str = "Hello, Serial!"; write(fd, str, strlen(str)); ``` 整个程序运行时,主线程可以继续执行其他任务,而串口数据的接收则在单独的线程中进行。 这样,就完成了一个简单的Linux线程实现串口通信的例程。 ### 回答2: 在Linux下,可以通过使用线程来实现串口通信。下面是一个简单的示例代码: ```c #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> #include <pthread.h> int fd; // 串口文件描述符 pthread_t thread_id; // 线程ID void* read_thread(void* arg) { char buf[255]; while(1) { int len = read(fd, buf, sizeof(buf)); // 从串口读取数据 if (len > 0) { buf[len] = '\0'; // 添加字符串结束符 printf("接收到的数据: %s\n", buf); } } } int main() { // 打开串口 fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { printf("无法打开串口\n"); return -1; } // 配置串口 struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); tcsetattr(fd, TCSANOW, &options); // 创建读取数据的线程 pthread_create(&thread_id, NULL, read_thread, NULL); // 主线程继续执行其他任务 while(1) { // 发送数据到串口 char msg[] = "Hello, Serial Port!"; write(fd, msg, sizeof(msg)); usleep(1000000); // 等待1秒 } // 关闭串口 close(fd); return 0; } ``` 在上述代码中,通过`open`函数打开了串口设备文件`/dev/ttyS0`(请根据实际情况更改),然后使用`termios`结构体配置了串口的波特率、数据位、停止位等属性。接下来,通过`pthread_create`函数创建了一个线程,该线程负责读取串口数据。主线程则负责发送数据到串口。 需要注意的是,该例程只是一个简单的示例,仅用于说明线程实现串口通信的基本思路。实际应用中,还需考虑数据的解析、错误处理、线程同步等问题。 ### 回答3: 在Linux下,可以使用串口通信库来实现线程的串口通信例程。下面是一个简单的例子: 1. 首先,我们需要安装和配置串口通信库。常用的库包括`libserialport`和`termios`。你可以使用包管理工具来安装这些库。 2. 在程序中,我们需要引入相关的头文件和库: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <serialport.h> ``` 3. 然后,创建一个线程来读取串口数据。可以定义一个函数作为线程入口点,例如`serial_read`: ``` void *serial_read(void *data) { struct sp_port *serial_port = (struct sp_port *)data; char buffer[256]; int n; while (1) { n = sp_blocking_read(serial_port, buffer, sizeof(buffer), 100); if (n > 0) { // 处理接收到的数据 printf("Received: %.*s\n", n, buffer); } else { // 读取数据出错或超时 printf("Serial read error or timeout\n"); } } return NULL; } ``` 4. 接下来,创建一个线程来发送串口数据。可以定义一个函数作为线程入口点,例如`serial_write`: ``` void *serial_write(void *data) { struct sp_port *serial_port = (struct sp_port *)data; char message[] = "Hello, Serial Port!\n"; while (1) { sp_nonblocking_write(serial_port, message, sizeof(message) - 1); usleep(1000000); // 暂停1秒钟 } return NULL; } ``` 5. 在主函数中,打开串口设备并创建两个线程: ``` int main() { struct sp_port *serial_port; pthread_t read_thread, write_thread; // 打开串口设备(例如:/dev/ttyS0) sp_get_port_by_name("ttyS0", &serial_port); sp_open(serial_port, SP_MODE_READ_WRITE); // 创建读取线程 pthread_create(&read_thread, NULL, serial_read, (void *)serial_port); // 创建写入线程 pthread_create(&write_thread, NULL, serial_write, (void *)serial_port); // 等待线程结束 pthread_join(read_thread, NULL); pthread_join(write_thread, NULL); // 关闭串口设备 sp_close(serial_port); sp_free_port(serial_port); return 0; } ``` 这是一个简单的例程,通过两个线程实现了线程的串口通信。读取线程通过不断调用`sp_blocking_read`函数读取串口数据,而写入线程通过不断调用`sp_nonblocking_write`函数发送串口数据。你可以根据需求来修改和扩展这个例程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值