进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身地址空间中的一次执行活动。
进程从来不执行任何东西,它是线程的容器。若要进程完成某项操作它必须拥有一个在它的环境中运行的线程,此线程负责包含执行包含在进程地址空间中的代码。
现存由线程的内核对象和线程栈两部分组成。操作系统用线程的内核对象来管理线程,内核对象也是系统用来存放线程统计信息的地方。线程栈用来维护程序在执行代码是所需要的所有函数参数和局部变量。
C++11之前,C++库中没有提供和线程相关的类或者接口,因此在编写多线程程序时,Windows上需要调用CreateThread创建线程。
C++11之后提供了thread线程类,可以很方便的编写多线程程序。下面代码中就是使用了thread类。
在讲线程同步问题时经常举得一个例子是火车票销售。我们现在也用这个例子简单的代码如下。总共有100张火车票,两个线程同步销售。
#include
#include
#include
using namespace std;
void fun1Proc();
void fun2Proc();
int tickets = 100;
void main()
{
thread t1(fun1Proc);
thread t2(fun2Proc);
t1.join();
t2.join();
system("pause");
}
void fun1Proc()
{
while (true)
{
if (tickets > 0)
{
this_thread::sleep_for(std::chrono::milliseconds(2));
std::cout << "Thread1 sell ticket" << tickets-- << std::endl;
}
else
break;
}
}
void fun2Proc()
{
while (true)
{
if (tickets > 0)
{
this_thread::sleep_for(std::chrono::milliseconds(2));
std::cout << "Thread2 sell ticket" << tickets-- << std::endl;
}
else
break;
}
}
上述代码因为有共享资源tickets,存在一个隐患是:当tickets==1时,如果线程1执行完if(tickets>0)就时间片到期了,操作系统会让线程2执行,而这时tickets的值还没有减1,线程2进行判断,满足大于0,然后卖票,减1,tickets=0,这时又轮到线程1运行,线程1从上次暂停的地方执行,打印出来0,即线程1卖了号码为0的票。(代码线程中加入sleep是为了便于复现这种问题。)
为了避免共享资源在使用期间被其他线程改变,需要在多个线程之间进行一个同步处理,保证一个线程访问共享资源时其他线程不能访问该资源。互斥锁(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
在上述代码中使用互斥对象,在main函数前定义全局变量互斥变量,代码如下
mutex mtx;
在线程1和线程2访问共享资源前(if(tickets>0)前 )等待互斥锁
mtx.lock();
线程1和线程2的break后,释放互斥锁。
mtx.unlock();
这时线程1运行到sleep时,暂停执行,线程2开始执行,执行到mtx.lock()时,因为线程1拥有互斥对象,线程2只能一直等待,直到线程1睡眠时间到了之后继续往下执行,最后释放互斥对象,这时线程2拥有了互斥对象的所有权,结束等待往下执行。不会出现两个线程同时访问资源而导致的异常问题。
但是互斥锁使用不当有时候也会造成死锁问题。这个将在另一篇文章里单独讲。