单核cpu需要对多线程访问共享资源加锁吗
需要。时间片完,也会引起线程的调度,需要对共享资源加锁。
线程参考
线程创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
thread: 传出参数,是无符号长整形数,线程创建成功,会将线程 ID 写入到这个指针指向的内存中
attr: 线程的属性,一般情况下使用默认属性即可,写 NULL
start_routine: 函数指针,创建出的子线程的处理动作,也就是该函数在子线程中执行。
arg: 作为实参传递到 start_routine 指针指向的函数内部
返回值:线程创建成功返回 0,创建失败返回对应的错误号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args){
printf("i am child thread%ld",pthread_self());
for(int i=0;i<10;i++)
printf("num:%d\n", i);
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid,NULL,working,NULL);
printf("thread create %ld\n",tid);
//
printf("i am father thread %ld\n",pthread_self());
for(int i=0;i<3;i++)
printf("num:%d\n",i);
//sleep(1);
return 0;
}
输出
thread create 139716524607232
i am father thread 139716524611392
num:0
num:1
num:2
子线程的并不输出,原因:
子线程被创建出来后,抢cpu时间片,抢不到就不运行。在子线程运行期间如果主线程退出,子线程会一并被销毁。
因为子线程使用的是主线程的虚拟地址空间。
解决方案:让子线程执行完毕,主线程再退出,可以在主线程中添加挂起函数 sleep();
进一步:
线程退出
加上这行 主线程调用退出函数退出, 地址空间不会被释放
pthread_exit(NULL);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args){
printf("i am child thread%ld",pthread_self());
for(int i=0;i<10;i++)
printf("num:%d\n", i);
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid,NULL,working,NULL);
printf("thread create %ld\n",tid);
//
printf("i am father thread %ld\n",pthread_self());
for(int i=0;i<3;i++)
printf("num:%d\n",i);
加上这行 主线程调用退出函数退出, 地址空间不会被释放
pthread_exit(NULL);
return 0;
}
pthread_exit()
线程退出但是不会导致虚拟地址空间的释放,针对于主线程,可以使用pthread_exit();
#include <pthread.h>
void pthread_exit(void *retval);
参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL
线程回收
子线程与主线程之间的通信
子线程退出的时候其内核资源由主线程回收
pthread_join();子线程还在运行则改函数阻塞直到子线程完成,调用一次回收一个,回收所有需要循环进行回收。第二个参数获取传递出来的数据(由pthread_exit(void** retvl)传出来的返回值)。
使用线程自己的栈传数据:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args)
{
int num=2;
pthread_exit(&num);
return NULL;
}
int main()
{
pthread_t tid[10];
for(int i=0;i<10;i++){
pthread_create(&tid[i], NULL, working, NULL);
printf("thread create %ld\n", tid[i]);
}
for(int i=0;i<10;i++){
void *N=NULL;
pthread_join(tid[i],&N);
printf("N:%d\n",*(int*)N);
}
//
printf("i am father thread %ld\n", pthread_self());
for (int i = 0; i < 3; i++)
printf("num:%d\n", i);
pthread_exit(NULL);
return 0;
}
输出:
N:32747
N:32767
N:32767
N:32767
N:32767
N:32767
N:32767
N:32767
N:32767
N:32767
数据保存在子线程自己的栈区,因为子线程销毁了,子线程的栈空间也归还给主线程,因此获取不到。
使用全局变量:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
int num=1;
void *working(void *args)
{
num*=2;
pthread_exit(&num);
return NULL;
}
int main()
{
pthread_t tid[10];
for(int i=0;i<10;i++){
pthread_create(&tid[i], NULL, working, NULL);
printf("thread create %ld\n", tid[i]);
}
for(int i=0;i<10;i++){
void *N=NULL;
pthread_join(tid[i],&N);
printf("N:%d\n",*(int*)N);
}
//
printf("i am father thread %ld\n", pthread_self());
for (int i = 0; i < 3; i++)
printf("num:%d\n", i);
pthread_exit(NULL);
return 0;
}
thread create 140322816030464
thread create 140322807637760
thread create 140322799245056
thread create 140322790741760
thread create 140322782349056
thread create 140322773956352
thread create 140322765563648
thread create 140322688464640
thread create 140322680071936
thread create 140322671679232
N:512
N:512
N:512
N:512
N:1024
N:1024
N:1024
N:1024
N:1024
N:1024
i am father thread 140322816034624
num:0
num:1
num:2
访问的是共享变量。void可以使用任意类型数据 void*为数据地址,void为指向数据地址的指针。
使用主线程的栈
创建子线程的时候传参数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args)
{
int *num=(int*)args;
*num*=2;
pthread_exit(num);
return NULL;
}
int main()
{
int num=1;
pthread_t tid[10];
for(int i=0;i<10;i++){
pthread_create(&tid[i], NULL, working, &num);
printf("thread create %ld\n", tid[i]);
}
for(int i=0;i<10;i++){
void *N=NULL;
pthread_join(tid[i],&N);
printf("N:%d\n",*(int*)N);
}
//
printf("i am father thread %ld\n", pthread_self());
for (int i = 0; i < 3; i++)
printf("num:%d\n", i);
pthread_exit(NULL);
return 0;
}
N:512
N:512
N:512
N:512
N:1024
N:1024
N:1024
N:1024
N:1024
N:1024
线程分离
一般情况下,主线程有自己的业务逻辑,如果主线程负责子线程的资源回收,调用pthread_join()只要子线程不退出,主线程就会一直被阻塞加粗样式,主线程就也被阻塞。
pthread_detach()线程分离函数,子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了。线程分离之后在主线程中使用 pthread_join() 就回收不到子线程资源了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args)
{
printf("i am child thread%ld", pthread_self());
sleep(3);
for (int i = 0; i < 10; i++)
printf("num:%d\n", i);
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("thread create %ld\n", tid);
pthread_detach(tid);
//
printf("i am father thread %ld\n", pthread_self());
for (int i = 0; i < 3; i++)
printf("num:%d\n", i);
pthread_exit(NULL);
return 0;
}
线程取消
在一个线程中杀死另一个线程。使用这个函数杀死一个线程需要分两步
1.A中调用pthread_cancel指定杀死线程B,B不死
2.B进行一次系统调用,被杀死
#include <pthread.h>
// 参数是子线程的线程ID
int pthread_cancel(pthread_t thread);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *working(void *args)
{
printf("i am child thread%ld", pthread_self());
sleep(10);
for (int i = 0; i < 10; i++)
printf("num:%d\n", i);
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("thread create %ld\n", tid);
pthread_cancel(tid);
//
printf("i am father thread %ld\n", pthread_self());
for (int i = 0; i < 3; i++)
printf("num:%d\n", i);
pthread_exit(NULL);
return 0;
}
子线程中cout进行了系统调用
thread create 140093522048768
i am child thread140093522048768i am father thread 140093522052928
num:0
num:1
num:2
线程 ID 比较
在 Linux 中线程 ID 本质就是一个无符号长整形,因此可以直接使用比较操作符比较两个线程的 ID,但是线程库是可以跨平台使用的,在某些平台上 pthread_t 可能不是一个单纯的整形,这中情况下比较两个线程的 ID 必须要使用比较函数,函数原型如下:
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
参数:t1 和 t2 是要比较的线程的线程 ID
返回值:如果两个线程 ID 相等返回非 0 值,如果不相等返回 0
c++线程类
c++11后增加了线程类
std::thread
- 构造函数
// ①
thread() noexcept;
// ②
thread( thread&& other ) noexcept;
// ③
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
// ④
thread( const thread& ) = delete;
1.构造函数:默认构造函数,构造一个线程对象,不执行任何处理动作
2.移动构造函数,把other的线程所有权转移给新的thread对象,之后other不再表示执行线程
3.创建线程对象,并在该线程中执行函数f中的业务逻辑,args是参数
f可以是,普通函数,类成员函数,匿名函数,仿函数,也可以是可调用对象包装器类型。
4.使用delete显示删除拷贝构造,不允许线程对象之间的拷贝。
成员函数:
- get_id() 获取线程id
std::thread::id get_id() const noexcept;
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
void func(int num,string str){
for(int i=0;i<10;i++){
cout<<"child thread i="<<i<<"num:"
<<num<<",str"<<str<<endl;
}
}
void func1(){
for(int i=0;i<10;i++){
cout<<"child thread i="<<i<<endl;
}
}
int main(){
cout<<this_thread::get_id()<<endl;
thread t(func,520,"i love u");
thread t1(func1);
t.join();
t1.join();
cout<<t.get_id()<<endl;
cout<<t1.get_id()<<endl;
return 0;
}
线程分离函数 detach () 不会阻塞线程,子线程和主线程分离之后,在主线程中就不能再对这个子线程做任何控制了,比如:通过 join () 阻塞主线程等待子线程中的任务执行完毕,或者调用 get_id () 获取子线程的线程 ID。有利就有弊,鱼和熊掌不可兼得,建议使用 join ()。
- joinable()
joinable() 函数用于判断主线程和子线程是否处理关联(连接)状态,一般情况下,二者之间的关系处于关联状态,该函数返回一个布尔类型:
返回值为 true:主线程和子线程之间有关联(连接)关系
返回值为 false:主线程和子线程之间没有关联(连接)关系
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
void foo(){
this_thread::sleep_for(std::chrono::seconds(1));
}
int main(){
thread t;
cout<<"before starting joinable"<<t.joinable()<<endl;
t=thread(foo);
cout << "before starting joinable" << t.joinable() << endl;
t.join();
cout << "before starting joinable" << t.joinable() << endl;
thread t1(foo);
cout << "before starting joinable" << t1.joinable() << endl;
t1.detach();
cout << "before starting joinable" << t1.joinable() << endl;
return 0;
}
//输出
0 1 0 1 0
- 创建时,未指定任务函数,子线程不启动也不绑定。
- 启动任务时,自动绑定。
- join()任务完成,子线程自动销毁也自动解除绑定。
- detach()父子线程分离,也就解除绑定。
operator
线程中的资源是不能被复制的,因此通过 = 操作符进行赋值操作最终并不会得到两个完全相同的对象
// move (1)
thread& operator= (thread&& other) noexcept;
// copy [deleted] (2)
thread& operator= (const other&) = delete;
通过以上 = 操作符的重载声明可以得知:
如果 other 是一个右值,会进行资源所有权的转移
如果 other 不是右值,禁止拷贝,该函数被显示删除(=delete),不可用