我们专注攀枝花网站设计 攀枝花网站制作 攀枝花网站建设
成都网站建设公司服务热线:400-028-6601

网站建设知识

十年网站开发经验 + 多家企业客户 + 靠谱的建站团队

量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决

C++线程操作-创新互联

文章目录
  • 一、线程和进程的基本概念
  • 二、C++中的线程
  • 三、互斥量mutex的使用
  • 四、条件变量condition_variable的使用
  • 五、如何获得线程的返回值
  • 六、线程的其他使用

创新互联-专业网站定制、快速模板网站建设、高性价比宕昌网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式宕昌网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖宕昌地区。费用合理售后完善,十多年实体公司更值得信赖。
一、线程和进程的基本概念

线程是操作系统进行运算调度的基本单位,进程是操作系统进行资源分配的基本单位。
不同进程分配不同的内存区域,同一个进程中的不同线程共享同一片内存。

线程的不同状态:

  1. 新建状态:仅仅是语言层面创建了线程状态,但是还没有与操作系统线程进行关联;
  2. 就绪状态:线程已经与操作系统相关联,可以被CPU进行调度;
  3. 运行状态:线程正占用着CPU,正在运算;
  4. 阻塞状态:等待被唤醒的状态;
  5. 终止状态:线程运行结束。

并行(Parallel)是真正意义上的同时运行,在同一时间点有多个程序在同时运行,比如多核CPU多个核同时运行着不同的程序。并发(ConCurrent)是在同一个时间段上多个程序同时运行,在宏观上看是同时运行,微观上是交替运行。

之所以设计多线程是为了充分的利用CPU资源,比如线程A正在等待磁盘数据的时候,可以将CPU让出来,线程B就能占用CPU进行计算。

二、C++中的线程

c++11新增了与操作系统无关的线程类thread,使用方法如下:

void func(int i) {cout<< i<< endl;
}

int main() {thread t1(func, 10);  //传入线程绑定的函数和函数所需参数
    t1.join();
    return 0;
}

thread对象创建之后,线程就已经进入就绪态,可以占用CPU进行运算。
线程的运行需要以具体的函数作为入口,而函数的参数传递无非值传递,引用传递和指针传递3种形式,值传递和指针传递没什么好说的,直接复制值或者指针放入线程入口函数中,而线程的使用中需要特别注意⚠️⚠️⚠️引用传递,普通函数使用引用传递,底层使用的还是指针,所以函数中的引用会关联到函数外的变量。但是在线程的使用中,函数引用传递是引用的复制,函数中的引用和函数外的值没有关联关系。如果想关联,必须传递参数的时候使用std::ref()语义进行包裹。

join()detach()的用法:调用线程的join()方法意味着,主线程会阻塞在该语句处,等待子线程运行结束的时候才会继续运行主线程,而detach()方法意味着,子线程从主线程中分离出来各自运行,子线程变为后台守护线程(deamon thread),如此这般,子线程可能会结束在主线程之后,可能会出现不可预知的问题。

三、互斥量mutex的使用

当存在多个线程共同访问同一资源(比如队列)的时候,如果操作的顺序不当可能会出现不可预知的错误,该资源成为临界资源,此时C++使用互斥量mutex进行资源访问的控制。

通常为了方便理解多线程操作,我们将mutex称为锁🔒,对资源的访问称为对资源上锁和解锁。实际上mutex是一个标记量,不同的线程对同一mutex对象进行lock()unlock()可以达到控制资源访问次序的目的。

lock()方法是尝试获取锁并加锁,如果获取不到锁,线程就会阻塞在此语句处,mutex调用了lock()就必须unlock(),否则就有可能是其他线程不能成功lock()而一直阻塞。
为了解决这种可能忘记unlock()的问题,引入lock_guard模版类,lock_guard类似于智能指针,出作用域会自动unlock()。和lock_guard相似的一个模版类是unique_lock,使用方法和lock_guard类似。

四、条件变量condition_variable的使用

多个线程需要通信的时候会用到cv,比如消费者和生产者线程模型,这两个线程并不是单单的对资源解锁和加锁。而是需要通信,比如,消费者没有资源可以消费的时候需要将生产者叫醒。

cv有两个方法wait()wait_for()wait方法会让当前线程释放锁然后阻塞,直到被唤醒;wait_for方法需要传递一个时间参数,当前线程释放锁然后等待被唤醒,但是不会一直等待,而是吵过时间参数自动苏醒。
cv还有两个方法notify_one()notify_all()notify_one会唤醒线程等待队列的第一个线程,而notify_all会唤醒所有等待的线程。

waitsleep方法的区别:

  1. waitcv的方法,sleepthis_thread的方法;
  2. wait会释放锁然后再阻塞,而sleep直接阻塞,不会释放锁。

五、如何获得线程的返回值

前面提到的线程入口函数都是实现一系列的操作但是没有返回值,如果需要线程的返回值,我们需要使用std::future<>这样一个模版类来接受返回值,而普通的用thread创建线程的方式不能获得future对象,此时有数种方法:

  1. 使用异步任务函数std::async(),顾名思义,异步任务就是启动一个和主线程并发的任务,该方法会返回一个future对象,通过该对象的get()方法就能获得线程返回的对象,使用方法如下:
int func() {return 1234;
}

int main() {std::futurefval = std::async(func);
    std::cout<< fval.get()<< std::endl;
    return 0;
}

主线程会阻塞在get()处,直到线程返回返回值。async()方法还可以传入一个枚举标志量std::launch,如果将此标记量设置为deferred,意味着方法调用处不会立即创建线程,直到调用get()方法才会创建方法并运行,而此时和直接在主线程调用线程入口函数是一样的,是串行运行。注意⚠️:get()方法只能调用一次;

2. 使用std::package_task<>包裹一个函数,然后传递给一个线程对象,线程运行起来之后用package_task对象的get_future()方法获得future对象,然后再用get()方法拿到返回值。package_task使用方法如下:

int func() {return 1234;
}

int main() {std::packaged_tasktask(func);
    std::thread t1(std::ref(task));
    t1.join();
    std::cout<< task.get_future().get()<< std::endl;
    return 0;
}

  1. 使用std::promise<>模版类可以从线程中将值带到线程外,promise对象会传递到线程入口函数中去,然后在线程运行过程中,使用set_value()方法给promise对象赋值,然后在异步线程之外,使用promise对象的get_future()方法获得future对象,之后的操作和以上就一样了。使用方法如下:
void func(std::promise&pro) {pro.set_value(100);
}

int main() {std::promisepro;
    std::thread t1(func, std::ref(pro));
    std::cout<< pro.get_future().get()<< std::endl;
    t1.join();  //此处使用join其实意义不大,因为get已经获取到值说明异步线程已经结束
    return 0;
}

六、线程的其他使用
  1. wait被唤醒之后第一件事是会反复尝试直到拿到锁再往下执行,wait被唤醒拿到锁之后一般还要再次检查阻塞条件保证多线程共享的正确性;
  2. unique_lock所有权的转移类似于unique_ptr,使用move进行所有权转移;
  3. std::lock(mutex1, mutex2)方法可以尝试同时锁住多个互斥量,如果有一个没锁成功,就不会对任何互斥量进行加锁;
  4. release()方法将unique_lockmutex解绑,并返回之前绑定的mutex
  5. 互斥量mutexlock()unlock()之间包裹的语句多少称为锁的粒度,粒度要合适,如果太小可能不能达到上锁的预期,如果太大,会降低多线程运行效率;
  6. std::adopt_lock可以传递给lock_guardunique_lock的构造函数,表示这个互斥量已经被lock了(你必须要把互斥量提前lock了 ,否者会报异常);std::adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(已经lock成功了);通知lock_guard不需要再构造函数中lock这个互斥量了。unique_lock也可以带std::adopt_lock标记,含义相同,就是不希望再unique_lock的构造函数中lock这个mutex。用std::adopt_lock的前提是,自己需要先把mutexlock上;用法与lock_guard相同;
  7. std::try_to_lock会尝试用mutex的lock去锁定这个mutex,但如果没有锁定成功,也会立即返回,并不会阻塞在那里,用这个try_to_lock的前提是你自己不能先lock;
  8. std::defer_lock的前提是,你不能自己先lock,否则会报异常,std::defer_lock的意思就是并没有给mutex加锁,初始化了一个没有加锁的mutex

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


名称栏目:C++线程操作-创新互联
浏览路径:http://shouzuofang.com/article/pcigi.html

其他资讯