【C++】多线程编程入门示例 1

①程序,进程,线程的区别

程序:程序是由一系列的指令和逻辑组成的一个静态文件(如cpp文件),无论能不能运行,它都客观的存在于储存器中。

进程:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位--来源于百度百科。如果你是初学者,可能你并不能真正地理解这句话。通俗地说,系统为特定的静态程序分配好运行时需要的各种资源,这个时候系统会连带地生成一个PCB(进程控制块,一种数据结构)用来记录程序运行时(这里的运行并不是指进程的运行态)的各种信息(如进程当前的状态等),这个时候你的程序就可以运行了,只需要等待CPU对其的调用,我们用进程来称呼其为程序的一次运行。

线程:在以前,进程是系统独立调度和分派的基本单位,后面由于多道处理的出现,产生了并发的概念(不明白并发的话可以先看第②点),它加大了系统的容量与对硬件的利用率。我们知道,对于单处理器的机器来说,实现并发典型的方法便是使用分时,即CPU将时间片按特定算法分发给各个进程,虽然总的计算次数可能并没有发生什么变化,但是由于CPU的计算速度越来越快,从宏观上来看,几个进程就像是在同一时间段内运行的。于是,当进程A的时间用完了之后就要切换到另一个进程B,此时计算机需要为进程A保存下结束时的状态以便下一次从上一次结束处继续执行,还需要为进程B的运行做各种准备工作,由于进程相对而言比较大,反复切换会浪费很多的资源,所以人们想能不能将系统独立调度和分派的基本单位做得更小,以减少进程切换所浪费的资源--于是线程出现了。现在,大部分的OS都是以线程为系统独立调度和分派的基本单位。另外,进程是由一个或者多个线程组成,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

②并发,并行的区别

并发:同一时间段内交替运行多个进程(线程)

并行:同一时刻运行多个进程(线程)。很明显,只有多处理器才能支持。

并发就像我们的大脑思考一样,同一个时刻只能想一件事,但是在很短的一个时间段内我们可以三心二意。当然如果你长了几个脑袋,那你就可以并行思考了呵呵。

③同步与异步,阻塞与非阻塞方式

让我们再来了解一下同步,异步,阻塞,非阻塞这个概念,加深对多线程编程的理解。

有了之前的概念,我们可以想象,当几个线程或者进程在并发执行时,如果我们不加任何干预措施,那么他们的执行顺序是由系统当时的环境来决定的,所以不同时间段不同环境下运行的顺序都会不尽相同,这便是异步(有差异的步骤)。当然,同步肯定就是通过一定的措施,使得几个线程或者进程总是按照一定顺序来执行(总是按照相同的步骤)。

当一个进程或者线程请求某一个资源而不得时,如I/O,便会进入阻塞状态,一直等待。scanf()便是一个很好的例子,当程序运行到scanf()时,如果输入缓存区为空,那么程序便会进入阻塞状态等待我们从键盘输入,这便是以阻塞的方式调用scanf()。
通过一定方法,我们可以将scanf()变成非阻塞的方式来执行。如给scanf()设置一个超时时间,如果时间到了还是没有输入那么便跳过scanf(),这个时候我们就称为用非阻塞的方式来调用scanf()。

对比可以发现,同步即阻塞。想要按照某特定顺序来执行一系列过程,在上一个过程完成之前下一个过程必须等待,这就是阻塞在了这个地方。当同步运行的时候,会等待同步操作完成才会返回,否则会一直阻塞在同步操作处。

相反的,异步即非阻塞,当异步调用某个函数时,函数会立刻返回,而不会阻塞在那。
怎么判断异步操作是否已经完成?通常有3种方式:
1.状态:异步操作完成时会将某个全局变量置为特定值,可以通过轮询判断变量的值以确定是否操作完成。
2.通知:异步操作完成会给调用者发送特定信号。
3.回调:异步操作完成时会调用回调函数。

所以同步即阻塞,异步即非阻塞。

class thread
{
    public:
        Thread();
        virtual ~Thread();
        int start (void * = NULL);
        void stop();
        void sleep (int);
        void detach();
        void * join();
        bool joinable();
    protected:
	virtual void * run(void *) = 0;
    private:
	...   //这里只介绍主要的方法,对于不同的库方法会有差异,不过都是大同小异。
}; 

stop(),sleep()这2个看名字就知道,一个停止,一个休眠等待。

start() 和run() 这2个是由区别的,执行 start() 会告诉系统创建一个新线程并就绪,但是并不一定马上运行,而是让系统选择一个合适的时间来调用 run() 来运行,这样就有了异步的可能。而 run() 的话就是将CPU腾出来立刻运行此线程,run() 可以用来确保线程的首次运行。

join() 和 detach() 这2个方法就显得更加难以理解了,且听我慢慢道来。

一个线程,总是会有下面2种状态之间的一种:

joinable:可会合的

detachable:分离的(不可会合的)

一个子线程被创建之后默认为 joinable ,在子线程终止之前,我们需要调用 join() 函数来将其与父线程会合,只有这样在子线程终止之后才能被摧毁,其所占有的资源(内存,端口等)才会被释放,否则会导致内存泄露。当然我们可以用 detach() 方法来将其设置为分离的,一个线程被分离之后将不再受我们的控制,可以想象成托管给了系统,当其被终止的时候会被马上摧毁。joinable() 方法可以用来检验当前是否为 joinable。

当然 join() 还被设置用来实现一个强大的功能--同步:

如果我们在主线程XX行调用了 join() ,那么在子线程终止之前,主线程会一直阻塞在XX行,这样就可以用来同步子线程和主线程了。


C++11中引入了一个用于多线程操作的thread类,简单多线程示例:

#include 
#include 
#include 
 
using namespace std;
 
//线程1
void thread01()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.join();
	task02.join();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
    return 0;
}

输出:

Thread 01 is working !Thread 02 is working !

Thread 01 is working !
Thread 02 is working !
Thread 01 is working !
Thread 01 is working !
Thread 02 is working !
Thread 01 is working !
Thread 02 is working !
Thread 02 is working !
Main thread is working !
Main thread is working !
Main thread is working !
Main thread is working !
Main thread is working !
请按任意键继续. . .

两个子线程并行执行,join函数会阻塞主线程,所以子线程都执行完成之后才继续执行主线程。


可以使用detach将子线程从主流程中分离,独立运行,不会阻塞主线程:

//线程2
void thread01()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.detach();
	task02.detach();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
    return 0;
}

输出:

Main thread is working !Thread 02 is working !Thread 01 is working !


Thread 01 is working !
Main thread is working !Thread 02 is working !
Thread 01 is working !

Thread 01 is working !
Thread 02 is working !
Main thread is working !
Thread 01 is working !
Thread 02 is working !
Main thread is working !
Thread 02 is working !Main thread is working !

请按任意键继续. . .

使用detach的主线程和两个子线程并行执行。


带参子线程

在绑定的时候也可以同时给带参数的线程传入参数:

//线程3 定义带参数子线程
void thread01(int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02(int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01, 5);  //带参数子线程
	thread task02(thread02, 5);
	task01.detach();
	task02.detach();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
    return 0;
}

输出:

Main thread is working !Thread 01 is working !

Thread 02 is working !
Thread 01 is working !
Thread 02 is working !
Main thread is working !
Thread 01 is working !
Thread 01 is working !
Thread 02 is working !
Main thread is working !
Thread 01 is working !
Thread 02 is working !
Main thread is working !
Thread 02 is working !
Main thread is working !
请按任意键继续. . .


多线程数据竞争

多个线程同时对同一变量进行操作的时候,如果不对变量做一些保护处理,有可能导致处理结果异常

//线程4 多线程数据竞争
int totalNum = 100; 
void thread01()
{
	while (totalNum > 0)
	{
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
	}
}
void thread02()
{
	while (totalNum > 0)
	{
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.detach();
	task02.detach();
	system("pause");
    return 0;
}

输出:

100
100
请按任意键继续. . . 98
97
96
95
94
94
9393

...

...

...


11
11
99

77

55

33

11

有两个问题,一是有很多变量被重复输出了,而有的变量没有被输出;二是正常情况下每个线程输出的数据后应该紧跟一个换行符,但这里大部分却是另一个线程的输出。这是由于第一个线程对变量操作的过程中,第二个线程也对同一个变量进行各操作,导致第一个线程处理完后的输出有可能是线程二操作的结果。针对这种数据竞争的情况,可以使用线程互斥对象mutex保持数据同步。

mutex类的使用需要包含头文件

#include

//线程5
mutex mu;  //线程互斥对象
int totalNum = 100;

void thread01()
{
	while (totalNum > 0)
	{
		mu.lock(); //同步数据锁
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
		mu.unlock();  //解除锁定
	}
}
void thread02()
{
	while (totalNum > 0)
	{
		mu.lock();
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
		mu.unlock();
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.detach();
	task02.detach();
	system("pause");
    return 0;
}

输出:

100
请按任意键继续. . . 99
98
97
96
95
94
93
92
91
90
.

.

.
6
5
4
3
2
1
0

你可能感兴趣的:(C++笔记)