自学内容网 自学内容网

# 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)!