操作系统实践06—线程

2023-05-16

操作系统实践06—线程

文章目录

  • 操作系统实践06—线程
  • 1.创建线程
    • 1.1 原型
    • 1.2 线程参数
    • 1.3 参数类型
    • 1.4 例子一
    • 1.5 例子二
  • 2. 等待线程
    • 2.1 原型
    • 2.2 线程返回值
    • 2.3 例子一
    • 2.4 例子二
  • 3. 线程互斥
    • 3.1 初始化互斥量
    • 3.2 加锁解锁
    • 3.3 例子一:不使用互斥量
    • 3.4 使用互斥量
  • 4. 条件变量
    • 4.1 初始化
    • 4.2 等待
    • 4.3 唤醒线程
    • 4.4 共享缓冲区
    • 4.5 例子一
    • 4.6 例子二

与线程管理相关的系统调用:

  • pthread_create
  • pthread_join
  • pthread_mutex_lock
  • pthread_mutex_unlock
  • pthread_cond_wait
  • pthread_cond_signal
  • pthread_cond_broadcast

1.创建线程

1.1 原型

#include <pthread.h>

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

功能:

  • 创建一个线程
  • 新线程从start_routine开始执行
  • 新线程的ID保存在tid指向的位置
参数功能
tid该参数是一个指针, 新线程的ID保存在tid指向的位置
attr线程属性。如果为空,则使用缺省的属性值
start_routine该参数是一个函数指针, 新线程从start_routine开始执行
arg提供给start_routine的参数

返回值:如果成功,返回0;如果失败,返回非0。

1.2 线程参数

创建线程时可以为线程入口函数提供参数

void *arg = "hello";
pthread_create(&tid, NULL, start_routine, arg);

线程入口函数接收类型为void *类型的参数

void *start_routine(void *arg)
{
    char *string = (char *)arg;
    puts(string); // 输出hello
}

1.3 参数类型

可以向线程入口函数传递任意类型的参数

  • 整型变量
int ivalue = 123;
void *arg = (void *) ivalue;
pthread_create(&tid, NULL, start_routine, arg);
  • 字符串变量
char *svalue = "string";
void *arg = (void *) svalue;
pthread_create(&tid, NULL, start_routine, arg);
  • 结构体变量,只能传递结构体的地址
struct person {
    char *name; 
    int age;
} p;
void *arg = (void *) &p;
pthread_create(&tid, NULL, start_routine, arg);

1.4 例子一

// ex1.c
#include <stdio.h>
// unistd.h包含了函数sleep的声明
#include <unistd.h>
// pthread.h包含了函数pthread_create的声明
#include <pthread.h>

void *compute(void *arg)
{
    char i;
    for (i = 'a'; i < 'd'; i++) {
        printf("worker: %c\n", i);
        sleep(1);
    }
    return NULL;
}

int main()
{ 
    pthread_t worker_tid;
    // 使用pthread_create创建一个新的工作线程
    // 现在程序中有两个线程,主线程和新创建的工作线程
    // 新创建的工作线程从函数compute开始执行,工作线程在循环中打印小写的字母a、b、c,每打印一行后,调用sleep睡眠一秒
    pthread_create(&worker_tid, NULL, &compute, NULL);

    // 主线程调用pthread_create后继续执行,主线程在循环中打印大写的字母A、B、C,每打印一行后,调用sleep睡眠一秒
    char i;
    for (i = 'A'; i < 'D'; i++) { 
        printf("master: %c\n", i);
        sleep(1);
    }
    return 0;
}

编译:

$ cc ex1.c
/tmp/cc7xcI6z.o: In function `main':
create.c:(.text+0x6f): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
# 编译程序报错,编译程序找不到函数pthread_create的定义

# 函数pthread定义在pthread线程库中,加入-lpthread选项后,可以正确编译程序了
$ cc ex1.c -lpthread
$ ./a.out
master: A
worker: a
master: B
worker: b
master: C
worker: c

1.5 例子二

// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void *compute(void *arg)
{
    char *string = arg;
    int i;

    for (i = 0; i < 3; i++) {
        puts(string);
        sleep(1);
    }
    return NULL;
}

int main()
{ 
    pthread_t worker_tid;
    pthread_create(&worker_tid, NULL, &compute, "worker"); 
    compute("master");
    return 0;
}

现在程序中有两个线程,主线程以"master"作为参数执行compute,新线程以"worker"作为参数执行compute。

$ cc ex2.c -lpthread
$ ./a.out
master
worker
master
worker
master
worker

2. 等待线程

2.1 原型

#include <pthread.h>

int pthread_join(pthread_t tid, void **result);

功能:等待线程结束。

参数:

参数功能
tid目标线程的ID
result用于存放线程的计算结果

返回值:如果成功,返回0;如果失败,返回非0。

2.2 线程返回值

线程入口函数返回类型为void **类型的结果

void *start_routine(void *arg)
{
    void *result;
    ...
    return result;
}

等待线程函数pthread_join获取线程的返回结果

void *result;
pthread_join(tid, &result);

2.3 例子一

计算array中这6个整数的累加和

// ex1.c
#include <stdio.h>
#include <pthread.h>

// 全局变量
int array[] = {1, 1, 1, 2, 2, 2};
#define NUMBER 6 

int worker_output;

void *worker(void *arg)
{
    int i;
	// 计算数组前3个整数的累加和,计算结果存放在变量worker_output
    for (i = 0; i < NUMBER / 2; i++)
        worker_output += array[i];
    return NULL;
}

int master_output;

void master()
{
    int i;
	// 计算数组后3个整数的累加和,计算结果存放在变量masert_output
    for (i = NUMBER / 2; i < NUMBER; i++)
        master_output += array[i];
}

int main()
{ 
    pthread_t worker_tid;
    int total;

    // 为了加快计算速度,采用两个线程进行计算
    // 第一个线程计算数组前3个整数的累加和
    // 第二个线程计算数组后3个整数的累加和
    pthread_create(&worker_tid, NULL, worker, NULL);
    master(); 
    // 等待子线程运行结束
    // worker_tid:等待线程的tid;NULL:忽略线程的返回值
    pthread_join(worker_tid, NULL);
    // 只有当子线程结束后,worker_output的值才是有效的
    total = master_output + worker_output;
    printf("master_output = %d, worker_output = %d, total = %d\n", master_output, worker_output, total);
    return 0;
}

程序编译

$ cc ex1.c -lpthread
$ ./a.out
master_output = 6, worker_output = 3, total = 9

2.4 例子二

计算数组array中全部元素的累加和。例子1使用全局变量保存线程的参数和计算结果,例子2使用局部变量保存线程的参数和计算结果。

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

int array[] = {1, 1, 1, 2, 2, 2};
#define NR_TOTAL 6
#define NR_CPU 2
#define NR_CHILD (NR_TOTAL/NR_CPU)

// array - 数组的地址
// start - 计算范围的起始位置
// end - 计算范围的结束位置
struct param {
    int *array;
    int start;
    int end;
};

// result - 计算结果累加和
struct result {
    int sum;
};

void *compute(void *arg)
{
    struct param *param;
    struct result *result;
    int sum = 0;
    int i;

    param = (struct param *)arg;
    for (i = param->start; i < param->end; i++)
        sum += param->array[i];

    // 使用malloc分配一块区域。主线程获取工作线程的计算结果后,需要将这块存储区域释放
    result = malloc(sizeof(struct result));
    result->sum = sum;
    return result;
}

int main()
{ 
    // 创建NR_CPU个线程,NR_CPU=2
    pthread_t workers[NR_CPU];
    struct param params[NR_CPU]; 
    int i;

    for (i = 0; i < NR_CPU; i++) {
        struct param *param;
        param = &params[i];
        param->array = array;
        // NH_CHILD等于每个线程需要累加的数组元素数量
        param->start = i * NR_CHILD; 
        param->end = (i + 1) * NR_CHILD;
        // 确定每个线程的计算范围,然后启动线程
        pthread_create(&workers[i], NULL, compute, param);
    }

    int sum = 0;
    for (i = 0; i < NR_CPU; i++) {
        struct result *result;
        // 调用pthread_join获取线程的计算结果
        pthread_join(workers[i], (void **)&result);
        sum += result->sum;
        // 线程使用malloc分配内存,用来存储计算结果;汇总计算结果后,需要将计算结果释放
        free(result);
    }

    printf("sum = %d\n", sum);
    return 0;
}

程序编译

$ cc result.c -lpthread
$ ./a.out
sum = 9

3. 线程互斥

3.1 初始化互斥量

// 原型
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能

  • pthread_mutex_init初始化互斥量
  • pthread_mutex_destroy释放互斥量

参数:如果attr等于NULL,则使用缺省的属性进行初始化

返回值:如果成功,返回0;如果失败,返回非0

3.2 加锁解锁

// 原型
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能

  • pthread_mutex_lock将互斥量加锁
  • pthread_mutex_unlock将互斥量解锁

返回值:如果成功,返回0;如果失败,返回非0

3.3 例子一:不使用互斥量

创建多个线程,对全局变量global进行并发修改

// ex1.c
#include <stdio.h>
#include <pthread.h>

volatile int global = 0;

void *compute(void *arg)
{
    int i;

    for (i = 0; i < 100 * 100 * 100; i++) {
        global++;
    }
    return NULL;
}

int main()
{ 
    int i;
    pthread_t tids[3];

    // 3个子线程会并发修改共享变量global
    global = 0;
    for (i = 0; i < 3; i++)
        pthread_create(&tids[i], NULL, compute, NULL);

    for (i = 0; i < 3; i++)
        pthread_join(tids[i], NULL);
    printf("global = %d\n", global);
    return 0;
}

编译执行这个程序,每个线程对global递增一百万次,期望global的值为三百万。由于并发修改造成程序的输出结果与预期不一致。

$ cc ex1.c -lpthread
$ ./a.out
global = 2971755 
$ ./a.out
global = 2843321

3.4 使用互斥量

// ex2.c
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex;  
volatile int global = 0;

void *compute(void *arg) 
{
    int i;

    for (i = 0; i < 100 * 100 * 100; i++) { 
        // 线程在访问global变量前先对互斥量加锁
        pthread_mutex_lock(&mutex); 
        global++; 
        // 线程在访问global变量后再对互斥量解锁
        pthread_mutex_unlock(&mutex); 
    }
    return NULL;
}

int main()
{ 
    int i;
    pthread_t tids[3];

    // 使用互斥量进行保护
    // 程序开始使用pthread_mutex_init初始化mutex
    pthread_mutex_init(&mutex, NULL); 
    for (i = 0; i < 3; i++)
        pthread_create(&tids[i], NULL, compute, NULL); 

    for (i = 0; i < 3; i++)
        pthread_join(tids[i], NULL); 
    // 程序结束使用pthread_mutex_destroy释放mutex
    pthread_mutex_destroy(&mutex); 

    printf("global = %d\n", global); 
    return 0;
}

编译程序

$ cc ex2.c -lpthread
$ ./a.out
global = 3000000
$ ./a.out
global = 3000000

4. 条件变量

4.1 初始化

// 原型
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);

功能

  • pthread_cond_init初始化条件变量
  • pthread_cond_destroy释放条件变量

参数:如果attr等于NULL,则使用缺省的属性进行初始化

返回值:如果成功,返回0;如果失败,返回非0

4.2 等待

// 原型
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

功能:阻塞当前线程的运行

参数:

参数功能
cond当前线程在条件变量上阻塞
mutex当前线程阻塞时所在的临界区

返回值:如果成功,返回0;如果失败,返回非0

4.3 唤醒线程

// 原型
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

功能

  • pthread_cond_signal唤醒阻塞在条件变量上的一个线程
  • pthread_cond_broadcast唤醒阻塞在条件变量上的所有线程

返回值:如果成功,返回0;如果失败,返回非0

4.4 共享缓冲区

变量out为共享缓冲区的读指针,变量in为共享缓冲区的写指针。初始化时,缓冲区的内容为空,此时in指针和out指针均为0

在这里插入图片描述

向缓冲区加入数据A,把数据A存入in指针指向的位置,in指针指向下一个位置。

在这里插入图片描述

向缓冲区加入数据B,把数据B存入in指针指向的位置,in指针指向下一个位置。

在这里插入图片描述

向缓冲区加入数据C,把数据C存入in指针指向的位置,in指针指向下一个位置。此刻缓冲区的状态为满,数组的长度为4,实际存储的元素的数目为3。

在这里插入图片描述

向缓冲区加入数据D,把数据D存入in指针指向的位置,in指针指向下一个位置。此时,in指针和out指针指向相同的位置,这个状态为非法状态。

在这里插入图片描述

4.5 例子一

使用条件变量实现生产者与消费者问题,存在一个共享缓冲区,生产者线程向共享缓冲区写数据,消费者线程从共享缓冲区读数据。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define CAPACITY 4
int buffer[CAPACITY];// 使用长度为4的数组表示共享缓冲区
int in;//变量in为共享缓冲去的写指针
int out;//变量out为共享缓冲去的读指针

// in指针和out指针相同时,缓冲区为空
int buffer_is_empty()
{
    return in == out;
}

//n指针和out指针相邻时,缓冲区为满
int buffer_is_full()
{
    return (in + 1) % CAPACITY == out;
}

int get_item()
{
    int item;
	// 获取out指针指向的元素
    item = buffer[out];
    // 移动out指针指向下一项
    out = (out + 1) % CAPACITY;
    return item;
}

void put_item(int item)
{
    // 将元素放置在in指针指向的位置
    buffer[in] = item;
    // 移动in指针指向下一项
    in = (in + 1) % CAPACITY;
}

pthread_mutex_t mutex;//使用mutex实现互斥访问共享变量in/out
pthread_cond_t wait_empty_buffer;//生产者需要等待条件变量wait_empty_buffer
pthread_cond_t wait_full_buffer;//消费者需要等待条件变量wait_full_buffer

#define ITEM_COUNT (CAPACITY * 2)

void *consume(void *arg)
{
    int i;
    int item;

    for (i = 0; i < ITEM_COUNT; i++) { 
        pthread_mutex_lock(&mutex);
        // 缓冲区为空,消费者等待条件变量wait_full_buffer
        while (buffer_is_empty())
            pthread_cond_wait(&wait_full_buffer, &mutex);

        item = get_item(); 
        printf("    consume item: %c\n", item); 

        // 释放一个空缓冲区,唤醒等待空缓冲区的生产者
        pthread_cond_signal(&wait_empty_buffer);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void *produce(void *arg)
{
    int i;
    int item;

    for (i = 0; i < ITEM_COUNT; i++) { 
        pthread_mutex_lock(&mutex);
        // 缓冲区为满,生产者等待条件变量wait_empty_buffer
        while (buffer_is_full()) 
            pthread_cond_wait(&wait_empty_buffer, &mutex);

        item = 'a' + i;
        put_item(item);
        printf("produce item: %c\n", item); 

        // 释放一个满缓冲区,唤醒等待满缓冲区的消费者
        pthread_cond_signal(&wait_full_buffer);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main()
{ 
    pthread_t consumer_tid;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&wait_empty_buffer, NULL);
    pthread_cond_init(&wait_full_buffer, NULL);

    // 创建消费者线程
    pthread_create(&consumer_tid, NULL, consume, NULL);
    // 主线程作为生产者,执行produce函数
    produce(NULL); 
    // 等待消费者线程结束
    pthread_join(consumer_tid, NULL);
    return 0;
}

编译输出如下

$ cc ex1.c -lpthread
$ ./a.out
produce item: a
produce item: b
produce item: c
    consume item: a
    consume item: b
    consume item: c
produce item: d
produce item: e
produce item: f
    consume item: d
    consume item: e
    consume item: f
produce item: g
produce item: h
    consume item: g
    consume item: h

4.6 例子二

实现功能同4.5,使用信号量完成同步。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define CAPACITY 4
int buffer[CAPACITY];//使用长度为4的数组表示共享缓冲区
int in;//变量in为共享缓冲去的写指针
int out;//变量out为共享缓冲去的读指针

// in指针和out指针相同时,缓冲区为空
int buffer_is_empty()
{
    return in == out;
}

// in指针和out指针相邻时,缓冲区为满
int buffer_is_full()
{
    return (in + 1) % CAPACITY == out;
}

int get_item()
{
    int item;

    item = buffer[out];
    out = (out + 1) % CAPACITY;
    return item;
}

void put_item(int item)
{
    buffer[in] = item;
    in = (in + 1) % CAPACITY;
}

// 使用条件变量实现信号量sema_t,value记录了信号量的值
typedef struct {
    int value;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} sema_t;

void sema_init(sema_t *sema, int value)
{
    sema->value = value;
    pthread_mutex_init(&sema->mutex, NULL);
    pthread_cond_init(&sema->cond, NULL);
}

// 如果信号量的值小于等于0,则等待条件变量
void sema_wait(sema_t *sema)
{
    pthread_mutex_lock(&sema->mutex);
    while (sema->value <= 0)
        pthread_cond_wait(&sema->cond, &sema->mutex);
    // 将信号量的值减一
    sema->value--;
    pthread_mutex_unlock(&sema->mutex);
}

void sema_signal(sema_t *sema)
{
    pthread_mutex_lock(&sema->mutex);
    // 将信号量的值加一
    ++sema->value;
    // 唤醒等待条件变量的线程
    pthread_cond_signal(&sema->cond);
    pthread_mutex_unlock(&sema->mutex);
}

sema_t mutex_sema;//mutex_sema用于互斥访问共享缓冲区变量in/out
// empty_buffer_sema/full_buffer_sema用于线程同步
sema_t empty_buffer_sema;
sema_t full_buffer_sema;

#define ITEM_COUNT (CAPACITY * 2)

void *consume(void *arg)
{
    int i;
    int item;
	
    // 消费者线程,从缓冲区中获取数据并打印
    for (i = 0; i < ITEM_COUNT; i++) { 
        // full_buffer_sema的数值表示空buffer的数量,申请请信号量full_buffer_sema
        sema_wait(&full_buffer_sema);
        sema_wait(&mutex_sema);

        item = get_item();
        printf("    consume item: %c\n", item); 

        sema_signal(&mutex_sema);
        // 消费者线程取走一个数据后,释放信号量empty_buffer_sema
        sema_signal(&empty_buffer_sema);
    }

    return NULL;
}

void *produce()
{
    int i;
    int item;

    // 生产者线程,从缓冲区中获取数据并打印
    for (i = 0; i < ITEM_COUNT; i++) { 
        // empty_buffer_sema的数值表示空buffer的数量,申请请信号量empty_buffer_sema
        sema_wait(&empty_buffer_sema);
        sema_wait(&mutex_sema);

        item = i + 'a';
        put_item(item);
        printf("produce item: %c\n", item); 

        sema_signal(&mutex_sema);
        // 生产者线程生产一个数据后,释放信号量full_buffer_sema
        sema_signal(&full_buffer_sema);
    }

    return NULL;
}

int main()
{ 
    pthread_t consumer_tid;
	
    // 初始化信号量
    sema_init(&mutex_sema, 1);
    sema_init(&empty_buffer_sema, CAPACITY - 1);
    sema_init(&full_buffer_sema, 0);

    // 创建消费者线程
    pthread_create(&consumer_tid, NULL, consume, NULL);
    // 主线程作为生产者,执行produce函数
    produce();
    // 等待消费者线程结束
    pthread_join(consumer_tid, NULL);
    return 0;
}

编译程序如下

$ cc ex2.c -lpthread
$ ./a.out
produce item: a
produce item: b
produce item: c
    consume item: a
    consume item: b
    consume item: c
produce item: d
produce item: e
produce item: f
    consume item: d
    consume item: e
    consume item: f
produce item: g
produce item: h
    consume item: g
    consume item: h
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

操作系统实践06—线程 的相关文章

随机推荐

  • FreeRTOS--资源管理

    函数重入 如果一个函数可以安全地被多个任务调用 xff0c 或是在任务与中断中均可调用 xff0c 则这个函数是可重入的 每个任务都单独维护自己的栈空间及其自身在的内存寄存器组中的值 如果一个函数除了访问自己栈空间上分配的数据或是内核寄存器
  • vscode代码提交到gittee码云 第一次提交方法

    学习3 xff1a 今天是第一次将vscode代码提交到gittee xff0c 废话不多说 xff0c 直接上方法 xff1a 查看git仓库 gt git status 将当前项目文件初始化为仓库 如果当前文件夹不是git仓库 xff0
  • 明火烟雾目标检测项目部署(YoloV5+Flask)

    明火烟雾目标检测项目部署 文章目录 明火烟雾目标检测项目部署1 拉取Docker PyToch镜像2 配置系统环境2 1 更换软件源2 2 下载vim2 3 解决vim中文乱码问题 3 运行项目3 1 拷贝项目到容器中3 2 安装项目所需的
  • 操作系统实践课作业(南航)

    操作系统实践课作业 xff08 南航 xff09 文章目录 操作系统实践课作业 xff08 南航 xff09 1 job21 1 main c1 2 math c1 3 Makefile 2 job32 1 myecho c2 2 myca
  • 在Linux系统下安装Neo4j图数据库

    在Linux系统下安装Neo4j图数据库 文章目录 在Linux系统下安装Neo4j图数据库1 Java JDK1 1 安装1 2 查看安装路径 2 Neo4j2 1 下载2 2 拷贝到容器中2 3 修改neo4j conf配置文件2 4
  • 大数定律 与 中心极限定理 的理解

    目录 1 大数定律 2 中心极限定理 1 大数定律 当样本的数量足够大时 xff0c 样本的统计特性就可以近似代表总体的统计特性 大数 是指样本的数量足够大或者试验的次数足够多 2 中心极限定理 设总体为 为总体的 N 个样本集 xff0c
  • 操作系统实践05—文件描述符和系统调用

    操作系统实践05 文件描述符和系统调用 文章目录 操作系统实践05 文件描述符和系统调用1 概念1 1 文件描述符1 2 系统调用1 3 例子 2 内核实现2 1 file结构体2 2 文件描述符表2 3 进程控制块2 4 私有的文件描述符
  • 医疗问答机器人项目部署

    医疗问答机器人项目部署 文章目录 医疗问答机器人项目部署1 拉取TensorFlow镜像2 配置系统环境2 1 更换软件源2 2 下载vim2 3 解决vim中文乱码问题2 4 安装Neo4J图数据库2 5 安装网络工具包 3 运行项目3
  • SimpleITK学习

    SimpleITK学习 文章目录 SimpleITK学习1 SimpleITK ReadImage path 2 SimpleITK GetArrayFromImage itk img 3 itk img GetOrigin 4 itk i
  • 【Docker】服务器部署项目

    服务器部署项目 文章目录 服务器部署项目1 远程连接服务器2 在Linux系统上安装Docker2 1 卸载旧版本2 2 使用 APT 安装2 3 安装Docker2 4 使用脚本自动安装2 5 启动Docker2 6 测试 Docker
  • 计算机网络04—网络层

    网络层 学习参考资料 xff1a 湖南科技大学 计算机网络谢希仁 计算机网络 xff08 第7版 xff09 文章目录 网络层1 概述1 1 IP协议及配套协议 2 两种服务2 1 面向连接的虚电路服务2 2 无连接的数据报服务2 3 对比
  • torch.nn学习

    torch nn学习 文章目录 torch nn学习1 卷积层1 1 Conv2d 2 池化层2 1 MaxPool2d2 2 MaxUnpool2d2 3 AvgPool2d 3 代码实践3 1 Inception Module3 2 R
  • 深度学习基础知识点【更新中】

    深度学习基础知识点 文章目录 深度学习基础知识点1 数据归一化2 数据集划分3 混淆矩阵4 模型文件5 权重矩阵初始化6 激活函数7 模型拟合8 卷积操作9 池化操作10 深度可分离卷积11 转置卷积 1 数据归一化 过大的输入数据未归一化
  • VS Code配置C/C++环境

    VS Code配置C C 43 43 环境 文章目录 VS Code配置C C 43 43 环境1 下载Visual Studio Code2 下载MinGW3 VS Code设置3 1 下载插件3 2 新建工作区3 3 C 43 43 环
  • 计算机网络05—运输层

    运输层 学习参考资料 xff1a 湖南科技大学 计算机网络谢希仁 计算机网络 xff08 第7版 xff09 文章目录 运输层1 概述1 1 两个主要协议1 2 端口 2 用户数据报协议UDP3 传输控制协议TCP3 1 概述3 2 可靠运
  • 【2022春招研发】字节笔试记录(测试方向)

    20220410字节笔试 测试方向 文章目录 20220410字节笔试 测试方向一 编程题2道 xff08 50分 xff09 二 单选题10道 xff08 20分 xff09 三 多选题10道 xff08 30分 xff09 一 编程题2
  • 浏览器主页被劫持篡改了怎么办

    就想下载个驱动 xff0c 结果一通操作把我的 Edge 浏览器主页篡改成了 桔梗网 xff0c 就下面这个网站 算了不喷它了 xff0c 来说说怎么改回去吧 其他浏览器的修改方式相同 找到 Microsoft Edge 浏览器的桌面快捷方
  • 【2022春实习】百度笔试记录(机器学习/数据挖掘/自然语言)

    20220412百度笔试 机器学习 数据挖掘 自然语言 文章目录 20220412百度笔试 机器学习 数据挖掘 自然语言一 选择题30道 xff08 60分 xff09 二 问答题1道 xff08 20分 xff09 三 系统设计题1道 x
  • 【算法工程师】华为技术面面试记录

    20220419华为技术面 面试岗位是算法工程师 文章目录 20220419华为技术面1 自我介绍 2 算法题3 专业知识3 1 数据结构3 2 计算机网络3 3 操作系统3 4 设计模式3 5 机器学习3 6 其他 4 提问环节 1 自我
  • 操作系统实践06—线程

    操作系统实践06 线程 文章目录 操作系统实践06 线程1 创建线程1 1 原型1 2 线程参数1 3 参数类型1 4 例子一1 5 例子二 2 等待线程2 1 原型2 2 线程返回值2 3 例子一2 4 例子二 3 线程互斥3 1 初始化