# issue 11 线程
一、线程概念
线程是在进程中产生的一个执行单元,是CPU 调度和分配的最小单元,其在同一个进程中
与其他线程并行运行,他们可以共享进程内的资源,比如内存、地址空间、打开的文件等等。
线程是CPU 调度和分派的基本单位,
进程是分配资源的基本单位
进程:正在运行的程序(狭义)是处于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称,从操作系统核心角度来说,进程是操作系统调度除CPU 时间片外进行的资源分配和保护的基本单位,它有一个独立的虚拟地址空间,用来容纳进程映像(如与进程关联的程序与数据),并以进程为单位对各种资源实施保护,如受保护地访问处理器、文件、外部设备及其他进程(进程间通信)
二、线程的创建与运行
pthread_create:
线程具有单独的执行流,因此需要单独定义线程的main 函数,还需要请求操作系统在
单独的执行流中执行该函数,完成该功能的函数如下。
#include<pthread.h>
int pthread_create(
pthread_t*restrict thread,
const pthread_attr_t * restrict attr,
void *(* start_routine)(void *),
void*restrict arg );
→成功时返回0,失败时返回其他值。
●thread
保存新创建线程ID 的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。
●attr
用于传递线程属性的参数,传递NULL 时,创建默认属性的线程。
●start routine
相当于线程main 函数的、在单独执行流中执行的函数地址值(函数指针)。
● arg
通过第三个参数传递调用函数时包含传递参数信息的变量地址值。
pthread_join
调用pthread_join 函数的进程(或线程)将进入等待状态,直到第一个参数为ID 的线程终
止为止。而且可以得到线程的main 函数返回值,所以该函数比较有用。下面通过示例了解
该函数的功能。
# include<pthread.h>
int pthread_join(pthread_t thread, void ** status);
→成功时返回e,失败时返回其他值。
thread 该参数值ID 的线程终止后才会从该函数返回。status 保存线程的main 函数返回值的
指针变量地址值。
示例代码:
#include<pthread.h>
void* threadEntry(void* arg) {
char* msg = "i am from thread!";
for (int i = 0; i < 5; i++) {
printf("%s(%d):%s thread info:%s\n", __FILE__, __LINE__, __FUNCTION__, arg);
}
return (void *)msg;
}
void lession94() {
pthread_t tid;
const char* pInfo = "hello world";
int ret=pthread_create(&tid,NULL, threadEntry,(void*)pInfo);
if (ret != -1) {
void* result = NULL;
pthread_join(tid, &result);
printf("%s(%d):%s from thread :%s\n", __FILE__, __LINE__, __FUNCTION__, result);
}
}
测试结果:
三、线程同步-互斥量
首先来看一段代码:
int num = 0;
void* thread_inc(void* arg) {
for (int i = 0; i < 100000; i++) {
num++;
}
return NULL;
}
void* thread_dec(void* arg) {
for (int i = 0; i < 100000; i++) {
num--;
}
return NULL;
}
void lession95() {
pthread_t thread_id[50];
for (int i = 0; i < 50; i++) {
if (i % 2)pthread_create(thread_id + i, NULL, thread_inc, NULL);
else pthread_create(thread_id + i, NULL, thread_dec, NULL);
}
for (int i = 0; i < 50; i++) {
pthread_join(thread_id[i], NULL);
}
printf("%s(%d):%s the num is: :%s\n", __FILE__, __LINE__, __FUNCTION__, num);
}
直接就发生了段错误,这是为什么呢,这就是因为num是全局变量,线程同时进行,同时读写num,没有同步导致的
互斥量是"Mutual Exclusion"的简写,表示不允许多个线程同时访问。互斥量主要用于解
决线程同步访问的问题。
互斥量和信号量很类似,但是仍然有区别,互斥量一次只会有一个线程进行操作,而信号量可能会启动很多个线程
修改后的代码:
int num = 0;
pthread_mutex_t mutex;
void* thread_inc(void* arg) {
pthread_mutex_lock(&mutex);
for (int i = 0; i < 100; i++) {
num++;
}
pthread_mutex_unlock(&mutex);
return NULL;
}
void* thread_dec(void* arg) {
pthread_mutex_lock(&mutex);
for (int i = 0; i < 100; i++) {
num--;
}
pthread_mutex_unlock(&mutex);
return NULL;
}
void lession95() {
pthread_mutex_init(&mutex,NULL);
pthread_t thread_id[50];
for (int i = 0; i < 50; i++) {
if (i % 2)pthread_create(thread_id + i, NULL, thread_inc, NULL);
else pthread_create(thread_id + i, NULL, thread_dec, NULL);
}
for (int i = 0; i < 50; i++) {
pthread_join(thread_id[i], NULL);
}
printf("%s(%d):%s the num is: :%d\n", __FILE__, __LINE__, __FUNCTION__, num);
pthread_mutex_destroy(&mutex);
}
测试结果:
四、线程同步-信号量
信号量与互斥量极为相似,在互斥量的基础上很容易理解信号量。
信号量创建及销毁方法如下:
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
→成功时返回0,失败时返回其他值。
Sem:创建信号量时传递保存信号量的变量地址值,销毁时传递需要销毁的信号量变量地址
值。
pshared :传递其他值时,创建可由多个进程共享的信号量;传递0 时,创建只允许1 个进
程内部使用的信号量。我们需要完成同一进程内的线程同步,所以传递0。
Value:指定新创建的信号量初始值。
上述函数的pshared 参数默认向其传递0。接下来介绍信号量中相当于互斥量lock、
unlock 的函数。
#include<semaphore.h>
int sem_post(sem_t*sem);int sem_wait(sem_t * sem);
→成功时返回0,失败时返回其他值。
Sem:传递保存信号量读取值的变量地址值,传递给sem_post 时信号量增1,传递给sem_wait
时信号量减1。
调用sem init 函数时,操作系统将创建信号量对象,此对象中记录着"信号量值"整数。
该值在调用sem post 函数时增1,调用sem_wait 函数时减1。但信号量的值不能小于0,因
此,在信号量为0 的情况下调用sem_wait 函数时,调用函数的线程将进入阻塞状态(因为
函数未返回)。
当然,此时如果有其他线程调用sem_post 函数,信号量的值将变为1,而原本阻塞的
线程可以将该信号量重新减为0 并跳出阻塞状态。实际上就是通过这种特性完成临界区的同
步操作,可以通过如下形式同步临界区(假设信号量的初始值为1)。
sem_wait(&sem);//信号量变为0...
// 临界区的开始
//......
//临界区的结束
sem_post(&sem);// 信号量变为1...
上面的代码结构中,调用sem_wait 函数进入临界区的线程在调用sem post 函数前不允
许其他线程进入临界区。
信号量的值在0 和1 之间跳转,因此,具有这种特性的机制称为"二进制信号量"。
"线程A 从用户输入得到值后存入全局变量mum,此时线程B 将取走该值并累加。
该过程共进行5 次,完成后输出总和并退出程序。"
实例代码:
#include <semaphore.h>
sem_t sem_one;//信号量
sem_t sem_two;
//int num = 0;
void* input_num(void* arg) {
int count = 0;
memcpy(&count, &arg, sizeof(int));//小顶端
//memcpy(&count, 4+(char*)&arg, sizeof(int));//大顶端
for (int i= 0; i < count; i++) {
printf("Input num:");
sem_wait(&sem_two);
scanf("%d", &num);
sem_post(&sem_one);
}
return NULL;
}
void lession96() {
int sum = 0;
int count = 5;
sem_init(&sem_one, 0, 0);//是否完成输入
sem_init(&sem_two, 0, 1);//是否完成计算
pthread_t thread;
pthread_create(&thread,NULL,input_num, reinterpret_cast<void*>(count));
for (int i = 0; i < count; i++) {
sem_wait(&sem_one);
sum += num;
sem_post(&sem_two);
}
printf("sum is:%d\n", sum);
sem_destroy(&sem_one);
sem_destroy(&sem_two);
}
测试结果:
五、线程的销毁
销毁线程的3 种方法
Linux 线程并不是在首次调用的线程main 函数返回时自动销毁,所以用如下2 种方法之一加
以明确。否则由线程创建的内存空间将一直存在。
1、调用pthread_join 函数。
2、调用pthread_detach 函数。
之前调用过pthread_join 函数。调用该函数时,不仅会等待线程终止,还会引导线程销毁。
但该函数的问题是,线程终止前,调用该函数的线程将进入阻塞状态。因此,通常通过如下
函数调用引导线程销毁。
#include <pthread.h>
int pthread_detach(pthread_t thread);
→成功时返回,失败时返回其他值。
thread 终止的同时需要销毁的线程ID。
调用pthread_detach 函数不会引起线程终止或进入阻塞状态,可以通过该函数引导销
毁线程创建的内存空间。
调用该函数后不能再针对相应线程调用pthread_join 函数,这需要格外注意。虽然还有
方法在创建线程时可以指定销毁时机,但与pthread_detach 方式相比,结果上没有太大差异.
原文地址:https://blog.csdn.net/weixin_46481335/article/details/144339237
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!