进程/线程
- 每一个程序在运行时,操作系统都会给这个程序分配一个进程,以32位的操作系统为例:就会分配4GB的虚拟内存空间(代码段、数据段、栈、堆),将程序的代码
加载到代码段
,并运行程序,执行程序的指令 -
线程的定义:
- 线程是基于进程的轻量级的
调度单元
,线程是在一个进程中创建出来的,当一个进程出来后,它本身就对应一个线程 - 一个进程还可以创建
多个线程
,这些线程共享进程的代码段
、堆
、数据段
,唯一不共享
的是栈
,这样每一个线程的函数调用与执行都是独立的互不影响 - 线程在执行过程中,随时可能挂起调度出去,所以当两个线程在访问共同的资源的时候要特别注意数据的同步
- 代码段、数据段、堆上的各个线程可以共享访问
- 线程是基于进程的轻量级的
代码部分
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>
static int g_value = 10;
char* ptr = NULL;
void test_func() {
}
// 开始运行我们的线程
// 独立的运行入口
// 共用了进程的代码段、数据段、堆
DWORD WINAPI thread_entry(LPVOID lpThreadParameter) {
g_value = 9; // 和进程公用数据段
ptr[0] = 10; // 和进程公用堆
test_func(); // 和进程公用代码段
// 每次要调度出去的时候,把自己的栈保存一下
// 每次调度回来的时候,又把栈恢复到调用之前
// 线程是OS独立的调度单元
while (1) {
printf("thread called\n");
Sleep(3000);
}
return 0;
}
int main(int argc, char** argv) {
ptr = malloc(100);
// 线程ID
int threadid;
// 创建线程的句柄
HANDLE h = CreateThread(NULL, 0, thread_entry, NULL, 0, &threadid);
// 主线程
while (1) {
printf("main thread\n");
Sleep(1500);
}
return 0;
}
运行结果

事件通知
需求:线程A,等待线程B完成达到某个条件时,才能够继续
场景:多媒体解码线程,等待输入线程输入数据,有数据了,再通知解码线程解码
- 创建一个事件,要求线程都可以访问
HANDLE CreateEvent( LPSECURITY_ATTRIBUTESlpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTRlpName)
函数说明:
- 第一个参数表示安全控制,一般传入NULL
- 第二个参数确定事件是否为手动重置,True表示手动重置。如果自动重置,则对改事件调用WaitForSingleObject()后会自动调用ResetEvent(),使事件变成未触发状态
- 第三个参数表示事件的初始状态,传入True表示已触发
- 第四个参数表示事件的名称,传入NULL表示匿名事件
- 设置等待线程
DWORD WINAPI WaitForSingleObject(HANDLE hHandle,DWORD Milliseconds)
函数说明:
- 第一个参数需要传递对应的事件
- 第二个参数填写等待时间(INFINITE代表一直等)
- 当条件满足后,触发线程
BOOL WINAPI SetEvent(HANDLE hEvent)
当条件满足时,直接使用SetEvent,传入对应的事件即可
- 代码实现部分
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>
HANDLE wait_cond = INVALID_HANDLE_VALUE;
// 开始运行我们的线程
// 独立的运行入口
// 共用了进程的代码段、数据段、堆
DWORD WINAPI thread_entry(LPVOID lpThreadParameter) {
Sleep(5000);
SetEvent(wait_cond);
// 每次要调度出去的时候,把自己的栈保存一下
// 每次调度回来的时候,又把栈恢复到调用之前
// 线程是OS独立的调度单元
while (1) {
printf("thread called\n");
Sleep(3000);
}
return 0;
}
int main(int argc, char** argv) {
// 不用手动重置这个事件
// 第二个参数 bManualReset : 是否人工重置
// 如果需要手动重置 则调用 ResetEvent(事件句柄)
wait_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
// 线程ID
int threadid;
// 创建线程的句柄
HANDLE h = CreateThread(NULL, 0, thread_entry, NULL, 0, &threadid);
printf("waiting...\n");
// 事件超时 INFINITE 代表一直等
WaitForSingleObject(wait_cond, INFINITE);
printf("waiting end...\n");
// 主线程
while (1) {
printf("main thread\n");
Sleep(1500);
}
return 0;
}
- 运行结果

这里我们可以看到,主线程会等待事件通知后接着运行
线程安全
为什么会有线程安全的问题:
当使用多线程维护同一个变量时,因为数据段、堆、代码段是共用的,所以会存在一个问题,2个线程或多个线程,同时在访问同一个资源的时候,由于线程之间随时会切换出去,所以就会导致他们访问同样的资源可能会冲突
如果线程与线程之间出现了冲突,那么我们这个时候需要加上一个锁
的机制:
- 需要请求一个资源的时候,先请求这个锁,一旦这个锁被占用了就等待
- 如果请求成功了以后,再处理,处理结束后释放这个锁
- 这样锁才能保证在处理共享资源时,同一时间只有一个线程在处理
- 我们称这种为线程同步、线程同步锁
- 锁
CRITICAL_SECTION lock; // 创建锁对象
InitializeCriticalSection(); // 初始化
EnterCriticalSection(); // 请求锁
LeaveCriticalSection(); // 释放锁
- 代码部分
EnterCriticalSection(&lock);
g_value = 9;
LeaveCriticalSection(&lock);
线程死锁
线程A 先拿锁1,再拿锁2,接着释放锁1,锁2
线程B 先拿锁2,再拿锁1,接着释放锁2,锁1
当线程A拿到了锁1,这时需要等锁2,刚好这时线程B拿到了锁2,需要等锁1。这时就引起了线程的死锁
所以我们在使用多个锁时,需要使用相同的顺序来请求锁和释放锁
Comments | NOTHING